patterns: Added DFIR Patterns (#442)

* Added /DFIR/ with patterns

Added /DFIR/ sub-directory.
Contains modified versions of built-in patterns for semi-automated Disk/Volume/Filesystem parsing geared towards Digital Forensics.
Originals in /fs/ should remain in tact for spot placement.

* DFIR_README.md

* DFIR_README.md

* DFIR_README.md

* DISK_PARSER.hexpat

* DISK_PARSER.hexpat

* FAT32.hexpat

* exFAT.hexpat

* README.md

Added DFIR related hexpats to table.

* README.md

---------

Co-authored-by: Xtreme-Liberty <59177844+Xtreme-Liberty@users.noreply.github.com>
This commit is contained in:
F01TECH
2025-12-05 15:18:56 -05:00
committed by GitHub
parent 681b1a1ded
commit 28a297582b
6 changed files with 3738 additions and 0 deletions

View File

@@ -0,0 +1,81 @@
ImHex Pattern Files - Digital Forensics:
- [ImHex-DFIR-Patterns](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns)
Enhanced features of the stock Disk/Filesystem pattern files for forensic review of disk content.
- [ImHex](https://github.com/WerWolv/ImHex)
- [ImHex Patterns](https://github.com/WerWolv/ImHex-Patterns)
Use:
- Open a physical disk via Raw Provider (read-only)
- EXAMPLE: /dev/disk6
- Import Pattern File
- EXAMPLE: DISK_PARSER.hexpat
- [Pattern_Selection (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/2-DISK_PARSER-Pattern.png)
- DISK_PARSER.hexpat
- Recognize MBR/GPT Disks and parse MPT/GPT
- Including Logical Volumes in an Extended Partition (container)
- Auto load file system patterns for FAT32, exFAT, NTFS formatted volumes
- Optional Disk Report
- [DISK > MBR/GPT (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/3-DISK-HYBRID.png)
- [DISK > MBR > MPT > 3 Primaries | 2 Logicals in an Extended (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/3a-DISK-MBR.png)
- FAT32.hexpat
- Auto loaded by DISK_PARSER.hexpat
- Parse VBR, FAT1, FAT2, Root Dir, and 1 level of SubDirs
- FAT1/FAT2 Cluster chaining with SFN resolution
- LFN/SFN Alias grouping in Root Dir
- Recognize deleted entries (xE5)
- File Content pointer
- D/T Conversions
- Optional FAT32 Volume Report
- [VOLUME > FAT32 > FAT1 (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/4-FAT32-1_SMALL_TXT.png)
- [VOLUME > FAT32 > Root Dir (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/5-FAT32_ROOT_DIR.png)
- [VOLUME > FAT32 > Data Pointer (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/6-FAT32_SFN_POINTER.png)
- exFAT.hexpat
- Auto loaded by DISK_PARSER.hexpat
- Parse VBR/Boot Sector/Extended Sectors, FAT1, Root Dir
- Recognize active directory entries (x85, xC0, xC1)
- Recognize inactive directory entries (x05, x40, x41)
- xC0/x40 File Content pointer
- D/T Conversions
- Optional exFAT Volume Report
- [VOLUME > exFAT (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/7-exFAT-1.png)
- [VOLUME > exFAT > Root Dir > xC0 (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/8-exFAT_xC0.png)
- [VOLUME > exFAT > Data Pointer (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/9-exFAT-Data_Pointer.png)
- NTFS.hexpat
- Auto loaded by DISK_PARSER.hexpat
- Parse VBR (Boot Sector), $MFT, Root Dir, and Indexes
- Recursively parse the $Metadata files, $Attributes, and user files/dirs
- Added file record | parent [MFT#] [SEQ#] indicators
- Parse x80/xB0 Data Runs
- File Content pointer
- D/T Conversions
- Optional NTFS Volume Report
- [VOLUME > NTFS > $MFT > D/T Conversion (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/10-NTFS-DT.png)
- [VOLUME > NTFS > $MFT > x80 Run List (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/11-NTFS-DATA_RUN.png)
- [VOLUME > NTFS > Data Pointer (screenshot)](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/screenshots/12-NTFS-DATA_POINTER.png)
- Optional Reports
- Simply copy the console output to a file...
- To enable/disable the reports:
- Open each DFIR related .hexpat
- Find the report constant (near the top)
- "true" = enabled
- "false" = disabled
Example Report: GPT > FAT32|exFAT
- [exFAT_Report](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/reports/exFAT_Report.txt)
Example Report: MBR > 5 Logical Volumes (2 in an Extended) > All FAT32 Volumes
- [MBR_5_VOLs](https://github.com/Xtreme-Liberty/ImHex-DFIR-Patterns/blob/main/reports/MBR_5_VOLs.txt)

View File

@@ -0,0 +1,677 @@
#pragma author Formula Zero One Technologies
#pragma description DFIR_DISK_PARSER_v2.0
#pragma MIME application/x-ima
#pragma endian little
// -----------------------------------------------------------------------------
// CREDIT
// -----------------------------------------------------------------------------
// Based on /fs/pattern.hexpat by WerWolv
// -----------------------------------------------------------------------------
// TODO
// -----------------------------------------------------------------------------
// Refine File System Detection/Match
// -----------------------------------------------------------------------------
// IMPORTS
// -----------------------------------------------------------------------------
import std.core;
import std.io;
import std.time;
import type.guid;
import type.magic;
import type.time;
import type.base;
import hex.provider;
// WORKING IMPORTS
import * from DFIR.FAT32 as FAT32Pat;
import * from DFIR.exFAT as EXFATPat;
import * from DFIR.NTFS as NTFSPat;
// ------------------------------------
// DISABLED IMPORTS
// REFS - UNTESTED
// EXT4 - GROUP DESC ERRORS
// APFS - PARTIALLY WORKS
// Comment out "using uuid_t = type::GUID"
// Replace all instances of "uuid_t" with "type::GUID"
// Comment out line 1456-EOF
// JPEG/PNG - OFFSET ERRORS
// ------------------------------------
//import * from fs.apfs as APFSPat;
//import * from fs.ext4 as EXT4Pat;
//import * from fs.refs as REFSPat;
//import * from jpeg as JPEGPat;
//import * from png as PNGPat;
// -----------------------------------------------------------------------------
// FWD DECs - GLOBAL
// -----------------------------------------------------------------------------
bool has_ext = false;
bool has_gpt = false;
u64 partitionOffset = 0;
u64 containerStartOffset = 0;
u32 mptIndex = 0;
u32 extIndex = 0;
str entryName = "";
u32 MPT_Count = 0;
u32 EXT_VolCount = 0;
u32 GPT_Count = 0;
u32 memory_size = std::mem::size();
str disk_path = hex::prv::get_information("file_path","");
u128 sector_size = hex::prv::get_information("sector_size","");
// -----------------------------------------------------------------------------
// REPORT HEADER ** ATTENTION **
// -----------------------------------------------------------------------------
// ---******---*******---vvvv--- |
const bool DISK_REPORT = true;
// ---******---*******---^^^^--- |
if (DISK_REPORT) {
std::print(" # # # # # # ");
std::print(" # # # ");
std::print(" # # # ");
std::print(" # # # # # # # ");
std::print(" I m H e x ");
std::print(" ");
std::print("-----------------------------------------");
std::print(" ");
std::print(" ENTITY: _____________________");
std::print(" ");
std::print("EXAMINER: _____________________");
std::print(" ");
u128 timestamp = std::time::epoch();
std::time::Time local_ts = std::time::to_local(timestamp);
std::time::Time utc_ts = std::time::to_utc(timestamp);
std::print("-----------------------------------------");
std::print(" LOCAL: {}",
std::format("{:02}/{:02}/{:04} @ {:02}:{:02}:{:02}",
local_ts.mon + 1,
local_ts.mday,
local_ts.year + 1900,
local_ts.hour,
local_ts.min,
local_ts.sec
));
std::print(" UTC: {}",
std::format("{:02}/{:02}/{:04} @ {:02}:{:02}:{:02}",
utc_ts.mon + 1,
utc_ts.mday,
utc_ts.year + 1900,
utc_ts.hour,
utc_ts.min,
utc_ts.sec
));
std::print("-----------------------------------------");
std::print(" ");
}
// -----------------------------------------------------------------------------
// SIGNATURE HELPER
// -----------------------------------------------------------------------------
enum MBRSignature : u16 {
MBR_SIG = 0xAA55 // 0x55AA -> Read LE
};
// -----------------------------------------------------------------------------
// CHS HELPER
// -----------------------------------------------------------------------------
bitfield CHS_Decoder {
head : 8;
sector : 6;
cylinder : 10;
} [[format("chs_formatter")]];
fn chs_formatter(CHS_Decoder CHS) {
return std::format("({:X}, {:X}, {:X}) | 0x{:X}", CHS.cylinder, CHS.head, CHS.sector, (CHS.cylinder * 16 + CHS.head) * 63 + (CHS.sector - 1));
};
// -----------------------------------------------------------------------------
// TIMESTAMP HELPER
// -----------------------------------------------------------------------------
struct DiskTimeStamp {
u8 seconds, minutes, hours;
};
// -----------------------------------------------------------------------------
// DISK PROTECTION HELPER
// -----------------------------------------------------------------------------
enum DiskProtection : u16 {
NotProtected = 0x0000,
CopyProtected = 0x5A5A
};
// -----------------------------------------------------------------------------
// PARTITION STATUS HELPER
// -----------------------------------------------------------------------------
enum PartitionStatus : u8 {
Not_Active = 0x00, // not_bootable
Active = 0x80 // bootable
};
enum MPTPartLabel : u8 {
UNUSED_OR_HIDDEN_ENTRY = 0x00,
PRIMARY = 0x07,
PRIMARY_F32_SMALL = 0x0B,
PRIMARY_0C_BIG = 0x0C,
EXTENDED_CONT_SMALL = 0x05,
EXTENDED_CONT_BIG = 0x0F,
LEGACY_MBR = 0xEE
};
// -----------------------------------------------------------------------------
// PARTITION TYPE HELPER
// -----------------------------------------------------------------------------
enum PartitionTypeCode : u8 {
UNUSED_ENTRY = 0x00,
FAT12_HDD = 0x01,
FAT12_HIDDEN = 0x11,
FAT16_SMALL = 0x04,
FAT16_SMALL_HIDDEN = 0x14,
FAT16_BIG = 0x06,
FAT16_BIG_HIDDEN = 0x16,
FAT32_SMALL = 0x0B,
FAT32_SMALL_HIDDEN = 0x1B,
FAT32_BIG = 0x0C,
FAT32_BIG_HIDDEN = 0x1C,
EXT_PART_SMALL = 0x05,
EXT_PART_SMALL_HIDDEN = 0x15,
EXT_PART_BIG = 0x0F,
EXT_PART_BIG_HIDDEN = 0x1F,
NTFS_EXFAT = 0x07,
NTFS_EXFAT_HIDDEN = 0x17,
WINDOWS_RECOVERY = 0x27,
NTFS_VOL_SET_1 = 0x86,
NTFS_VOL_SET_2 = 0x87,
macOSX = 0xA8,
OS2_HIDDEN_CDRIVE = 0x84,
LINUX_EXT = 0x83,
LINUX_EXT2 = 0x85,
LINUX_LVM = 0x8E,
LINUX_PA_RISC = 0xF0,
LINUX_RAID = 0xFD,
FREE_BSD = 0xA5,
OPEN_BSD = 0xA6,
QNX_1 = 0x4D,
QNX_2 = 0x4E,
QNX_3 = 0x4F,
GPT_DISK_STD = 0xEE,
GPT_DISK_SYS = 0xEF,
UNKNOWN = 0xFF,
};
// -----------------------------------------------------------------------------
// GUID PARTITION TABLE (GPT) PARTIONING SCHEME RELATED
// -----------------------------------------------------------------------------
// V V V V V V V V V V
// -----------------------------------------------------------------------------
// GPT PARTITION LABEL HELPER
// -----------------------------------------------------------------------------
enum GUIDPartLabel : u128 {
// ---------------- COMMON ----------------
UNUSED_ENTRY = 0x00000000000000000000000000000000,
EFI_SYSTEM_PART = 0x3BC93EC9A0004BBA11D2F81FC12A7328,
APPLE_APFS_CONT = 0xACEC4365300011AA11AA00007C3457EF,
APPLE_HFS_PLUS_PART = 0xACEC4365300011AA11AA000048465300,
MICROSOFT_RESERVED_PART = 0xAE1502F92DF97D81B84D5C0BE3E3C9E3,
WINDOWS_REC_ENVIRONMENT = 0xACD67901D5BF6AA1404DD106A4BB94DE,
BASIC_DATA_PART = 0xC79926B7B668C0874433B9E5EBD0A0A2,
// ---------------- LINUX ----------------
LINUX_FILE_SYSTEM = 0xE47D47D8693D798E477284830FC63DAF,
RAID_PART = 0x1E91840F3F7406A04D3B05FCA19D880F,
ROOT_PART_X86 = 0x8A45F0D531D1F79A41B2F29744479540,
ROOT_PART_X86_64 = 0x09B784F9CAFBE7964DB1E8CD4F68BCE3,
ROOT_PART_ARM = 0xD3BE9AD4A1216CB14E3C2CE469DAD710,
ROOT_PART_ARM_64 = 0xAE3F0D286F4C44AF41C31DF0B921B045,
BOOT_PART = 0x72716FFD75B252A3426259E6BC13C2FF,
SWAP_PART = 0x4F4F4BC83309E58443C4A4AB0657FD6D,
LOGICAL_VOLUME_MGR_PART = 0x28F93D2A8F233CA244C2F507E6D6D379,
HOME_PART = 0x15F9AEE2140E44B84F132EB4933AC7E1,
SRV_SERVER_DATA_PART = 0xE8986FA7251A7F904F3B20E03B8F8425,
PLAIN_DMCRYPT_PART = 0xB786550AA13E418949B72D007FFEC5C9,
LUKS_PART = 0xCC59605342171C864C5363EDCA7D7CCB,
// ---------------- APPLE ----------------
APPLE_UFS_CONT = 0xACEC4365300011AA11AA000055465300,
APPLE_ZFS = 0x316673200008A69911B21DD26A898CC3,
APPLE_RAID_PART = 0xACEC4365300011AA11AA000052414944,
APPLE_RAID_PART_OFFLINE = 0xACEC4365300011AA11AA5F4F52414944,
APPLE_BOOT_PART_REC_HD = 0xACEC4365300011AA11AA0000426F6F74,
APPLE_LABEL = 0xACEC4365300011AA11AA6C004C616265,
APPLE_TV_RECOVERY_PART = 0xACEC4365300011AA11AA76655265636F,
APPLE_CORE_STORAGE_CONT = 0xACEC4365300011AA11AA616753746F72,
HFS_FILEVAULT_VOLUME_CONT = 0xACEC4365300011AA11AA616753746F72,
APPLE_APFS_PREBOOT_PART = 0xACEC4365300011AA11AA006769646961,
APPLE_APFS_RECOVERY_PART = 0xACEC4365300011AA11AA007972637652,
// ---------------- WINDOWS ----------------
LOGICAL_DISK_MGR_META_PART = 0xB3CF34E104E1D28542E08F7EAAC80858,
LOGICAL_DISK_MGR_DATA_PART = 0xAD694A71113368BC4F621431A0609BAF,
IBM_GENERAL_PARALLEL_FILE_SYS_PART = 0x74B155E07A2DC3914E4EEF7D90FFAA37,
STORAGE_SPACES_PART = 0x2DECF6E501B0A3AFEE4CF6808FAF5CE7,
STORAGE_REPLICA_PART = 0xD123292BD147C8AAC043A1ACC58D4355,
};
// -----------------------------------------------------------------------------
// BASIC DATA PARTITION ATTRIBUTES
// -----------------------------------------------------------------------------
bitfield GPT_BDP_Attributes {
bool platform_required : 1 [[comment("Bit 0: RequiredPartition - Volume must be preserved")]];
bool io_ignore : 1 [[comment("Bit 1: NoBlockIOProtocol - EFI ignores this Volume, no FS Mapping")]];
bool legacy_flag : 1 [[comment("Bit 2: LegacyBIOSBootable - Active/Bootable under BIOS")]];
reserved_UEFI : 45 [[comment("Bits 347: Reserved for UEFI")]];
reserved_MS : 12 [[comment("Bits 4859: Reserved for Microsoft")]];
bool read_only : 1 [[comment("Bit 60: BasicDataPart - Read-Only Volume")]];
bool shadow_copy : 1 [[comment("Bit 61: BasicDataPart - Shadow Copy Volume")]];
bool hidden : 1 [[comment("Bit 62: BasicDataPart - Hidden Volume")]];
bool no_drive_letter : 1 [[comment("Bit 63: BasicDataPart - Do not Auto-Assign Drive Letter")]];
} [[bitfield_order(
std::core::BitfieldOrder::LeastToMostSignificant, 64)]];
// -----------------------------------------------------------------------------
// GPT ENTRIES PARSER
// LBA2-LBA33
// EACH ENTRY IS 128 BYTES (DESCRIBES A VOLUME)
// -----------------------------------------------------------------------------
union PartitionUnion {
le type::GUID PartTypeGUID; // HUMAN READABLE GUID
GUIDPartLabel PartTypeLabel [[name(std::format("PartTypeLabel (DERIVED)"))]]; // OBJECT LABEL
};
struct GPT_PartitionEntry {
PartitionUnion Type [[comment("Known Partition Type GUID: Global Identifier")]];
le type::GUID Unique_GUID [[comment("Unique Partition GUID: Every Volume has its own Unique GUID")]];
u64 Start_LBA [[comment("The first Sector of the Volume (Offset by 1)")]];
u64 End_LBA [[comment("The last Sector of the Volume (Offset by 1)")]];
GPT_BDP_Attributes ATTR [[comment("ATTRs for a Basic Data Partition may not be the same as a Microsoft Reserved Partition")]];
char16 PartName[36] [[comment("Partition Name: Based on Known Partition Type GUID, except for Disk Images")]];
if (Type.PartTypeLabel != GUIDPartLabel::UNUSED_ENTRY) {
GPT_Count += 1;
}
u64 GPTpartitionOffset = Start_LBA * sector_size
[[name(std::format("VOL_OFFSET {} | 0x{:02X} (DERIVED)", Start_LBA * sector_size, Start_LBA * sector_size)),
export]];
match (Type.PartTypeLabel) {
(GUIDPartLabel::UNUSED_ENTRY):
continue;
(GUIDPartLabel::EFI_SYSTEM_PART):
FAT32Pat EFI_SYS_VOL @ GPTpartitionOffset;
(GUIDPartLabel::BASIC_DATA_PART |
GUIDPartLabel::WINDOWS_REC_ENVIRONMENT): {
char gpt_fat32_magic[8] @ GPTpartitionOffset + 82 [[hidden]];
char gpt_ntfs_magic[8] @ GPTpartitionOffset + 3 [[hidden]];
char gpt_exfat_magic[8] @ GPTpartitionOffset + 3 [[hidden]];
if (gpt_fat32_magic == "FAT32 ")
FAT32Pat FAT32_VOL @ GPTpartitionOffset;
if (gpt_ntfs_magic == "NTFS ")
NTFSPat NTFS_VOL @ GPTpartitionOffset;
else if (gpt_exfat_magic == "EXFAT ")
EXFATPat EXFAT_VOL @ GPTpartitionOffset;
}
// --------- DISABLED -----------------
// EXT4 PATTERN WAS INOP WHEN TESTED
//(GUIDPartLabel::LINUX_FILE_SYSTEM):
//EXT4Pat EXT4_VOL @ GPTpartitionOffset;
//(GUIDPartLabel::APPLE_APFS_CONT):
// APFSPat APFS_VOL @ GPTpartitionOffset;
}
} [[name(std::format("GPT_ENTRY [{}]", std::core::array_index()))]];
// -----------------------------------------------------------------------------
// GPT HEADER PARSER
// LBA1 OFFSETS 0-91 (92 bytes of 512 bytes used)
// -----------------------------------------------------------------------------
struct GPT_Header {
type::Magic<"EFI PART"> signature [[comment("Signature (EFI PART)")]];
u32 revision [[comment("Header Revision Value")]];
u32 header_size [[comment("Size of Header - 92 Bytes")]];
type::Hex<u32> header_crc32 [[comment("GPT Header Checksum")]];
u32 reserved [[comment("Zeros")]];
u64 current_lba [[comment("Current LBA - GPT Header Location")]];
u64 backup_lba [[comment("Location of Backup - Header & GPT")]];
u64 first_usable_lba [[comment("1st Sector Available for Logical VOL")]];
u64 last_usable_lba [[comment("Last Sector Available for Logical VOL")]];
type::GUID disk_guid [[comment("Unique Disk GUID")]];
u64 partition_entries_lba [[comment("1st Sector of GPT")]];
u32 num_partition_entries [[comment("Total Number of Partition Entries Available - 128 on Windows")]];
u32 size_of_partition_entry [[comment("Size in Bytes of each GPT Entry")]];
type::Hex<u32> partition_entries_crc32 [[comment("GPT Array Checksum")]];
};
// -----------------------------------------------------------------------------
// MASTER BOOT RECORD (MBR) PARTIONING SCHEME RELATED
// -----------------------------------------------------------------------------
// V V V V V V V V V V
// -----------------------------------------------------------------------------
// MASTER PARTITION TABLE (MPT)
// LBA0 > 0FFSETS 446-509
// Each Entry Describes a Logical Volume (type/start_loc/size)
// -----------------------------------------------------------------------------
union MBRPartitionUnion {
PartitionTypeCode Part_Type;
MPTPartLabel PartTypeLabel; // overlay for 0x00
};
struct PartitionTableEntry {
// partition table fields
PartitionStatus ActiveFlag;
CHS_Decoder Starting_CHS;
MBRPartitionUnion Type;
CHS_Decoder Ending_CHS;
u32 Start_LBA;
u32 Total_Sectors;
if (Type.PartTypeLabel != MPTPartLabel::UNUSED_OR_HIDDEN_ENTRY) {
// Track Count of Logical Volumes in the Extended Container
//MPT_Count += 1;
if (containerStartOffset == 0) {
// top-level MBR entry
MPT_Count = MPT_Count + 1;
} else {
// a logical inside an extended container
EXT_VolCount = EXT_VolCount + 1;
}
}
partitionOffset = containerStartOffset + (Start_LBA * sector_size);
match (Type.PartTypeLabel) {
(PartitionTypeCode::UNUSED_ENTRY): continue;
(PartitionTypeCode::FAT32_SMALL | PartitionTypeCode::FAT32_BIG): {
FAT32Pat FAT32_VOL @ partitionOffset;
}
(PartitionTypeCode::NTFS_EXFAT): {
char magic[8] @ partitionOffset + 3;
if (magic == "NTFS ")
NTFSPat NTFS_VOL @ partitionOffset;
else
EXFATPat EXFAT_VOL @ partitionOffset;
}
(PartitionTypeCode::EXT_PART_SMALL | PartitionTypeCode::EXT_PART_BIG): {
// Save parent state
bool parent_has_ext = has_ext;
has_ext = true;
containerStartOffset = partitionOffset;
// Parse first two entries of the extended partition
PartitionTableEntry EXTENDED_PARTITION[2] @ partitionOffset + 446;
has_ext = parent_has_ext;
}
(PartitionTypeCode::GPT_DISK_STD | PartitionTypeCode::GPT_DISK_SYS):
// Set global flag
has_gpt = true;
}
if (!has_ext) {
entryName = std::format("MPT_ENTRY [{}]", mptIndex);
mptIndex += 1;
} else {
if (std::core::array_index() <= 0) {
entryName = std::format("LOGICAL_VOL (EXT) [{}]", extIndex);
} else if (std::core::array_index() == 1) {
entryName = "NEXT VOL POINTER (EXT)";
} else {
entryName = std::format("LOGICAL_VOL (EXT) [{}]", extIndex);
}
extIndex += 1;
}
} [[name(entryName)]];
// -----------------------------------------------------------------------------
// MBR PARSER
// LBA0 > OFFSETS 0-511 (512 bytes)
// -----------------------------------------------------------------------------
struct MasterBootRecord {
u8 bootstrapCodeArea1[218] [[comment("Boot Strapping Code")]];
padding[2] [[comment("Zeros")]];
u8 originalPhysicalDrive [[comment("???")]];
DiskTimeStamp diskTimeStamp [[comment("Timestamp of Disk OG Partitioning")]];
u8 bootstrapCodeArea2[216] [[comment("Boot Strapping Code")]];
u32 diskSignature [[comment("Disk Signature")]];
DiskProtection diskProtection [[comment("Disk Protection - 0x0000=Not | 0x5A5A=Prot")]];
PartitionTableEntry PT[4] [[comment("Master Partition Table (MPT) Offset 446-509")]];
MBRSignature MBR_SIG [[comment("End of MBR - 0x55AA")]];
};
// -----------------------------------------------------------------------------
// DISK PARSER
// -----------------------------------------------------------------------------
struct DiskRoot {
// Master Boot Record at LBA 0 (1st physical sector)
MasterBootRecord MBR @ 0x00;
if (has_gpt) {
// GPT Header at LBA 1 (2nd physical sector)
GPT_Header GPT_HEADER @ 0x200;
// The GPT (table) at LBA 2 (3rd physical sector) to LBA 33 (34th physical sector)
// 32 sectors total (Windows) that can define up to 128 - (primary) logical volumes
GPT_PartitionEntry GPT_ENTRIES[GPT_HEADER.num_partition_entries] @ (GPT_HEADER.partition_entries_lba * 512);
}
};
// -----------------------------------------------------------------------------
// ROOT OBJECT
// -----------------------------------------------------------------------------
// ---
DiskRoot DISK @ 0x0;
// ---
// ------------------------------
// DISK REPORT
// ------------------------------
if (DISK_REPORT) {
std::print("-----------------------------------------");
std::print("-------------- DISK_REPORT --------------");
std::print("-----------------------------------------");
// Disk Basics
std::print("DISK_PATH = {}", disk_path);
std::print("SECTOR_SIZE = {} BYTES", sector_size);
std::print("DISK_SIZE = {} SECTORS", memory_size / sector_size);
std::print("DISK_SIZE = {:.4f} GB @ 1000", memory_size / 1000.0 / 1000.0 / 1000.0);
std::print("DISK_SIZE = {:.4f} GiB @ 1024", memory_size / 1024.0 / 1024.0 / 1024.0);
// Disk Protection
str diskProtectionStr;
if (DISK.MBR.diskProtection == DiskProtection::NotProtected) {
diskProtectionStr = "NOT_COPY_PROTECTED";
} else if (DISK.MBR.diskProtection == DiskProtection::CopyProtected) {
diskProtectionStr = "COPY_PROTECTED";
} else {
diskProtectionStr = "UNKNOWN";
}
std::print("DISK_PROTECT = {}", diskProtectionStr);
// Partition Scheme
if (MPT_Count >= 1 && GPT_Count == 0) {
std::print("PART_SCHEME = MBR");
} else if (GPT_Count >= 1 && MPT_Count == 0) {
std::print("PART_SCHEME = GPT");
} else if (GPT_Count >= 1 && MPT_Count >= 1) {
std::print("PART_SCHEME = HYBRID (MBR + GPT)");
} else {
std::print("PART_SCHEME = UNKNOWN");
}
// MBR MPT Partitions
for (u32 i = 0, i < MPT_Count, i = i + 1) {
std::print("-----------------------------------------");
std::print("-------------- MBR_MPT [{}] --------------", i);
std::print("-----------------------------------------");
// STATUS
str statusStr;
if (DISK.MBR.PT[i].ActiveFlag == PartitionStatus::Active) {
statusStr = "ACTIVE/BOOTABLE";
} else if (DISK.MBR.PT[i].ActiveFlag == PartitionStatus::Not_Active) {
statusStr = "INACTIVE/NOT_BOOTABLE";
} else {
statusStr = "UNKNOWN";
}
std::print(" STATUS = {}", statusStr);
// TYPE_CODE
str typeStr;
if (DISK.MBR.PT[i].Type.Part_Type == PartitionTypeCode::FAT32_SMALL) {
typeStr = "FAT32 (CHS) (0x0B)";
} else if (DISK.MBR.PT[i].Type.Part_Type == PartitionTypeCode::FAT32_BIG) {
typeStr = "FAT32 (LBA) (0x0C)";
} else if (DISK.MBR.PT[i].Type.Part_Type == PartitionTypeCode::NTFS_EXFAT) {
typeStr = "NTFS/EXFAT (0x07)";
} else if (DISK.MBR.PT[i].Type.Part_Type == PartitionTypeCode::GPT_DISK_STD) {
typeStr = "GPT_PROTECTIVE (0xEE)";
} else if (DISK.MBR.PT[i].Type.Part_Type == PartitionTypeCode::EXT_PART_BIG) {
typeStr = "EXTENDED (0x0F)";
} else {
typeStr = "OTHER/UNKNOWN";
}
std::print(" TYPE_CODE = {}", typeStr);
// LBA and size
std::print(" FIRST_LBA = {:02}", DISK.MBR.PT[i].Start_LBA);
std::print(" LAST_LBA = {:02}", DISK.MBR.PT[i].Start_LBA + DISK.MBR.PT[i].Total_Sectors - 1);
std::print(" VOL_SIZE = {:02} SECTORS", DISK.MBR.PT[i].Total_Sectors);
std::print(" VOL_SIZE = {:.4f} GB", (DISK.MBR.PT[i].Total_Sectors * sector_size) / 1000.0 / 1000.0 / 1000.0);
std::print(" VOL_SIZE = {:.4f} GiB", (DISK.MBR.PT[i].Total_Sectors * sector_size) / 1024.0 / 1024.0 / 1024.0);
if (DISK.MBR.PT[i].Type.PartTypeLabel == MPTPartLabel::EXTENDED_CONT_SMALL ||
DISK.MBR.PT[i].Type.PartTypeLabel == MPTPartLabel::EXTENDED_CONT_BIG) {
u32 logicalCount = std::core::member_count(DISK.MBR.PT[i].EXTENDED_PARTITION);
//u32 logicalCount = std::mem::size(DISK.MBR.PT[i].EXTENDED_PARTITION);
for (u32 e = 0, e < logicalCount, e = e + 1) {
if (DISK.MBR.PT[i].EXTENDED_PARTITION[e].Type.PartTypeLabel == MPTPartLabel::UNUSED_OR_HIDDEN_ENTRY)
continue;
std::print("-----------------------------------------");
std::print("---------- LOGICAL (EXT) [{}] ------------", e);
std::print("-----------------------------------------");
// STATUS
str EXTstatusStr;
if (DISK.MBR.PT[i].EXTENDED_PARTITION[e].ActiveFlag == PartitionStatus::Active) {
EXTstatusStr = "ACTIVE/BOOTABLE";
} else if (DISK.MBR.PT[i].EXTENDED_PARTITION[e].ActiveFlag == PartitionStatus::Not_Active) {
EXTstatusStr = "INACTIVE/NOT_BOOTABLE";
} else {
EXTstatusStr = "UNKNOWN";
}
std::print(" STATUS = {}", EXTstatusStr);
// TYPE_CODE
str EXTtypeStr;
if (DISK.MBR.PT[i].EXTENDED_PARTITION[e].Type.Part_Type == PartitionTypeCode::FAT32_SMALL) {
EXTtypeStr = "FAT32 (CHS) (0x0B)";
} else if (DISK.MBR.PT[i].EXTENDED_PARTITION[e].Type.Part_Type == PartitionTypeCode::FAT32_BIG) {
EXTtypeStr = "FAT32 (LBA) (0x0C)";
} else if (DISK.MBR.PT[i].EXTENDED_PARTITION[e].Type.Part_Type == PartitionTypeCode::NTFS_EXFAT) {
EXTtypeStr = "NTFS/EXFAT (0x07)";
} else if (DISK.MBR.PT[i].EXTENDED_PARTITION[e].Type.Part_Type == PartitionTypeCode::GPT_DISK_STD) {
EXTtypeStr = "GPT_PROTECTIVE (0xEE)";
} else if (DISK.MBR.PT[i].EXTENDED_PARTITION[e].Type.Part_Type == PartitionTypeCode::EXT_PART_BIG) {
EXTtypeStr = "EXTENDED (0x0F)";
} else {
EXTtypeStr = "OTHER/UNKNOWN";
}
std::print(" TYPE_CODE = {}", EXTtypeStr);
std::print(" FIRST_LBA = {}", DISK.MBR.PT[i].EXTENDED_PARTITION[e].Start_LBA);
std::print(" LAST_LBA = {}", DISK.MBR.PT[i].EXTENDED_PARTITION[e].Start_LBA +
DISK.MBR.PT[i].EXTENDED_PARTITION[e].Total_Sectors - 1);
std::print(" VOL_SIZE = {} SECTORS", DISK.MBR.PT[i].EXTENDED_PARTITION[e].Total_Sectors);
std::print(" VOL_SIZE = {:.4f} GB",
(DISK.MBR.PT[i].EXTENDED_PARTITION[e].Total_Sectors * sector_size) / 1000.0 / 1000.0 / 1000.0);
std::print(" VOL_SIZE = {:.4f} GiB",
(DISK.MBR.PT[i].EXTENDED_PARTITION[e].Total_Sectors * sector_size) / 1024.0 / 1024.0 / 1024.0);
}
}
}
// GPT Header
if (GPT_Count >= 1) {
std::print("-----------------------------------------");
std::print("-------------- GPT_HEADER ---------------");
std::print("-----------------------------------------");
std::print("SIGNATURE = {}", DISK.GPT_HEADER.signature);
std::print("REVISION = 0x{:02X}", DISK.GPT_HEADER.revision);
std::print("GPT_HDR_CRC = 0x{:02X}", DISK.GPT_HEADER.header_crc32);
std::print("GPT_HDR_BACKUP_LBA = {}", DISK.GPT_HEADER.backup_lba);
std::print("DISK_GUID = {}", DISK.GPT_HEADER.disk_guid);
std::print("FIRST_USABLE_LBA = {}", DISK.GPT_HEADER.first_usable_lba);
std::print("LAST_USABLE_LBA = {}", DISK.GPT_HEADER.last_usable_lba);
std::print("MAX_GPT_ENTRIES = {:02}", DISK.GPT_HEADER.num_partition_entries);
std::print("GPT_ENTRY_SIZE = {:02} BYTES", DISK.GPT_HEADER.size_of_partition_entry);
std::print("GPT_ARRAY_CRC = 0x{:02X}", DISK.GPT_HEADER.partition_entries_crc32);
// GPT Partitions
for (u32 j = 0, j < GPT_Count, j = j + 1) {
std::print("-----------------------------------------");
std::print("------------- GPT_PART [{}] --------------", j);
std::print("-----------------------------------------");
std::print(" PART_TYPE_LABEL = {}", DISK.GPT_ENTRIES[j].Type.PartTypeLabel);
std::print(" PART_TYPE_GUID = {}", DISK.GPT_ENTRIES[j].Type.PartTypeGUID);
std::print(" UNIQUE_PART_GUID = {}", DISK.GPT_ENTRIES[j].Unique_GUID);
std::print(" FIRST_LBA = {:02}", DISK.GPT_ENTRIES[j].Start_LBA);
std::print(" LAST_LBA = {:02}", DISK.GPT_ENTRIES[j].End_LBA);
bool _any = false;
std::print(" ATTR_FLAGS |");
if(DISK.GPT_ENTRIES[j].ATTR.platform_required) {
std::print(" |- - - - > PlatformRequired");
_any = true;
}
if(DISK.GPT_ENTRIES[j].ATTR.io_ignore) {
std::print(" |- - - - > NO_FS_MAP");
_any = true;
}
if(DISK.GPT_ENTRIES[j].ATTR.legacy_flag) {
std::print(" |- - - - > LEGACY_BOOT");
_any = true;
}
if(DISK.GPT_ENTRIES[j].Type.PartTypeLabel == GUIDPartLabel::BASIC_DATA_PART) {
if(DISK.GPT_ENTRIES[j].ATTR.read_only) {
std::print(" |- - - - > READ_ONLY");
_any = true;
}
if(DISK.GPT_ENTRIES[j].ATTR.shadow_copy) {
std::print(" |- - - - > SHADOW_COPY");
_any = true;
}
if(DISK.GPT_ENTRIES[j].ATTR.hidden) {
std::print(" |- - - - > HIDDEN");
_any = true;
}
if(DISK.GPT_ENTRIES[j].ATTR.no_drive_letter) {
std::print(" |- - - - > NO_AUTO_MOUNT");
_any = true;
}
}
// if nothing was printed, say "NONE"
if (!_any) {
//std::print(" |> NONE");
std::print(" |- - - - > NONE");
}
std::print(" PART_TYPE_NAME = {}", DISK.GPT_ENTRIES[j].PartName);
}
}
std::print("-----------------------------------------");
std::print("------------------ END ------------------");
std::print("-----------------------------------------");
std::print(" ");
}

789
patterns/DFIR/FAT32.hexpat Normal file
View File

@@ -0,0 +1,789 @@
#pragma author Formula Zero One Technologies
#pragma description FAT32 File System (FAT32_v2.0)
#pragma MIME application/x-ima
#pragma endian little
// -----------------------------------------------------------------------------
// CREDIT
// -----------------------------------------------------------------------------
// OG AUTHOR: WerWolv
// OG DESC: fs/fat32.hexpat_v1.0
// -----------------------------------------------------------------------------
// NOTES FOR v2.0 ** GLOBALS NEED YOUR INPUT **
// -----------------------------------------------------------------------------
// Imported by DISK_PARSER.hexpat
// Added section separators for organization
// Added recursive parsing for Root Dir and a next level
// Added D/T conversions
// Show filenames on hover
// Added comments to DFIR fields of interest
// Changed pattern output naming/structure.
// Parse FAT1/FAT2
// Show SFN <-> Starting Cluster Relation Overlay
// -----------------------------------------------------------------------------
// TODO
// -----------------------------------------------------------------------------
// Parse all SFN/LFN entries, not just Root + 1
// -----------------------------------------------------------------------------
// IMPORTS
// -----------------------------------------------------------------------------
import std.core;
import std.io;
import std.mem;
import std.time;
import std.string;
import type.time;
// -----------------------------------------------------------------------------
// FORWARD DECS/GLOBALS
// -----------------------------------------------------------------------------
// *** ATTENTION ***
// SET MAXIMUM NUMBER OF 4 BYTE CHUNKS TO PARSE FROM FAT1
// DEFAULT IS 4096
// Choose a value greater than 1 and less than 65536 OR increase the Array size limit with "#define... "
// -------**************---vvvv--- |
const u64 MAX_FAT_CHUNKS = 4096;
// -------**************---^^^^--- |
// *** ATTENTION ***
// SET MAXIMUM NUMBER OF SFN = STARTING CLUSTER TO PROCESS
// DEFAULT IS 100 (2 LEVELS DEEP | ROOT DIR + 1)
// Choose a value greater than 1 and less than 65536 OR increase the Array size limit with "#define... "
// ---**************---************---vvv--- |
const u64 MAX_SFN_CLUSTER_RELATIONS = 100;
// ---**************---************---^^^--- |
// ---*******---*******----vvvv--- |
const bool VOLUME_REPORT = true;
// ---*******---*******----^^^^--- |
u64 bytesPerCluster = 0;
u64 rootDirSectors = 0;
u64 firstDataSector = 0;
u64 dataRegionStart = 0;
u64 sfn_count = 0;
u64 sfn_del_count = 0;
u64 lfn_count = 0;
u64 lfn_del_count = 0;
u64 start_index = 0;
u64 root_dir_start = 0;
u64 allocated_file_count = 0;
u64 VBR_OFFSET = 0;
u64 FAT1_start_offset = 0;
u64 FAT2_start_offset = 0;
u64 FAT_ClusterHeap_Count = 0;
u64 abs_FAT1_start_offset = 0;
u64 abs_FAT2_start_offset = 0;
u64 abs_rootDirStart_offset = 0;
// -----------------------------------------------------------------------------
// FILE ALLOCATION TABLE RELATED
// -----------------------------------------------------------------------------
// V V V V V V V V V V
// -----------------------------------------------------------------------------
// ------------------------------
// SFN <-> CLUSTER OVERLAY
// ------------------------------
struct INFO_Overlay {
u64 index = std::core::array_index();
u64 start_location = FAT1_start_offset + 8 + (index * 4);
u32 current_cluster = 2 + index;
str filename = overlay_func_name(current_cluster);
if (filename != "") {
char hover_label[4] @ start_location [[
name(std::format(
"SFN: {} | CLUSTER {}",
filename,
current_cluster
))]];
}
} [[inline]];
fn overlay_func_name(u32 cluster_num) {
str fname = "";
str ext = "";
str combo = "";
// Loop through all ROOT_DIR_ENTRIES
for (u32 i = 0, i < std::core::member_count(ROOT_DIR_ENTRIES), i = i + 1) {
// Check SFN_ALIAS and SFN_ENTRY in root entries
if (std::core::has_member(ROOT_DIR_ENTRIES[i], "SFN_ALIAS")) {
if (ROOT_DIR_ENTRIES[i].SFN_ALIAS.first_cluster == cluster_num) {
combo = std::format("{}.{}",
ROOT_DIR_ENTRIES[i].SFN_ALIAS.fileName,
ROOT_DIR_ENTRIES[i].SFN_ALIAS.extension);
return combo;
}
} else if (std::core::has_member(ROOT_DIR_ENTRIES[i], "SFN_ENTRY")) {
if (ROOT_DIR_ENTRIES[i].SFN_ENTRY.first_cluster == cluster_num) {
combo = std::format("{}.{}",
ROOT_DIR_ENTRIES[i].SFN_ENTRY.fileName,
ROOT_DIR_ENTRIES[i].SFN_ENTRY.extension);
return combo;
}
}
// Loop through all SUB_DIR_INDEX arrays for this root entry
if (std::core::has_member(ROOT_DIR_ENTRIES[i], "SUB_DIR_INDEX")) {
for (u32 j = 0, j < std::core::member_count(ROOT_DIR_ENTRIES[i].SUB_DIR_INDEX), j = j + 1) {
if (std::core::has_member(ROOT_DIR_ENTRIES[i].SUB_DIR_INDEX[j], "SFN_ALIAS")) {
if (ROOT_DIR_ENTRIES[i].SUB_DIR_INDEX[j].SFN_ALIAS.first_cluster == cluster_num) {
combo = std::format("{}.{}",
ROOT_DIR_ENTRIES[i].SUB_DIR_INDEX[j].SFN_ALIAS.fileName,
ROOT_DIR_ENTRIES[i].SUB_DIR_INDEX[j].SFN_ALIAS.extension);
return combo;
}
} else if (std::core::has_member(ROOT_DIR_ENTRIES[i].SUB_DIR_INDEX[j], "SFN_ENTRY")) {
if (ROOT_DIR_ENTRIES[i].SUB_DIR_INDEX[j].SFN_ENTRY.first_cluster == cluster_num) {
combo = std::format("{}.{}",
ROOT_DIR_ENTRIES[i].SUB_DIR_INDEX[j].SFN_ENTRY.fileName,
ROOT_DIR_ENTRIES[i].SUB_DIR_INDEX[j].SFN_ENTRY.extension);
return combo;
}
}
}
}
}
return ""; // no match found
};
// -----------------------------------------------------------------------------
// FAT32 FILE ALLOCATION TABLE (FAT) PARSER
// -----------------------------------------------------------------------------
const u32 CLUSTER_SIZE_BYTES = 4; // Each FAT32 entry = 4 bytes
const u32 FAT32_EOF = 0x0FFFFFFF; // End-of-file marker
const u32 FAT32_BAD = 0x0FFFFFF7; // Bad cluster marker
const u32 FIRST_ALLOC_CLUSTER = 2; // First usable cluster after reserved
enum FAT_Flags : u32 {
UNALLOCATED = 0x00000000,
END_OF_FILE = 0x0FFFFFFF, // L.END
BAD_CLUSTER = 0xFFFFFFF7, // L.END
//POINTER = Num >= 1 // INFO
};
union FAT_Union {
u32 DECIMAL [[hidden]];
FAT_Flags FAT_FLAG;
};
// ------------------------------
// Helper function for pointer label
// ------------------------------
fn cluster_label(u32 val) {
if (val == FAT_Flags::UNALLOCATED)
return "UNALLOCATED";
if (val == FAT_Flags::BAD_CLUSTER)
return "BAD";
if (val >= 0x0FFFFFF8)
return "EOF";
return std::format("{}", val);
};
// ------------------------------
// FAT1/FAT2 HEAPS/CHAINS
// ------------------------------
struct FAT_Entry {
FAT_Union FAT [[inline]];
u32 cluster_num = (FIRST_ALLOC_CLUSTER) + (std::core::array_index());
u32 next_cluster = FAT.DECIMAL & 0x0FFFFFFF;
char hover_label[4] @ $ - 4 [[
name(std::format(
"Cluster: {} → {}",
cluster_num,
cluster_label(next_cluster)
))
]];
bool is_eof = next_cluster >= 0x0FFFFFF8;
bool is_bad = next_cluster == FAT32_BAD;
bool is_free = next_cluster == 0;
if (is_eof) {
allocated_file_count += 1;
}
} [[name(format_fat_entry(FAT.DECIMAL, std::core::array_index(), FIRST_ALLOC_CLUSTER))]];
// ------------------------------
// FAT FORMATTER FUNC
// ------------------------------
fn format_fat_entry(u32 raw_value, u32 cluster_index, u32 first_alloc_cluster) {
u32 next_cluster = raw_value & 0x0FFFFFFF;
str next_label;
if (next_cluster == 0)
next_label = "UNALLOCATED";
else if (next_cluster == FAT32_BAD)
next_label = "BAD";
else if (next_cluster == 0x0FFFFFFF)
next_label = "EOF";
else
next_label = std::format("{}", next_cluster);
u32 logical_cluster = first_alloc_cluster + cluster_index;
if (next_label == "UNALLOCATED" || next_label == "BAD" || next_label == "EOF")
return std::format("Cluster {}: {}", logical_cluster, next_label);
else
return std::format("Cluster {} → {}", logical_cluster, next_label);
};
// ------------------------------
// MEDIA DESCRIPTOR HELPER
// ------------------------------
enum Media_Descriptor : u8 {
SINGLE_SIDE_FLOPPY = 0xF0,
DOUBLE_SIDE_FLOPPY = 0xF9,
HARD_DISK_DRIVE = 0xF8,
};
// ------------------------------
// FAT1/FAT2 HEADER PARSER
// ------------------------------
struct FAT_Header {
Media_Descriptor mediaDescriptor [[comment("0xF8=FIXED DISK | 0xF0=REMOVABLE")]];;
u8 FAT32_FAT_HEADER[7] [[comment("8 BYTES TOTAL: 4 BYTES REPRESENT PSUEDO CLUSTER 0 (SYSTEM) | 4 BYTES REPRESENT PSUEDO CLUSTER 1 (SYSTEM)(EOF)")]];
char root_dir_label[4] @ $ [[
name(std::format(
"ROOT_DIRECTORY"
))
]];
// WHICH IS WHY THE ROOT DIRECTORY (FIRST DATA AREA ITEM) STARTS IN CLUSTER 2
};
// -----------------------------------------------------------------------------
// ROOT DIRECTORY RELATED
// -----------------------------------------------------------------------------
// V V V V V V V V V V
// -----------------------------------------------------------------------------
// ------------------------------
// ACTIVE LFN SEQUENCE NUMBER BITFIELD
// * EXCEPT DELETED ENTRIES - 0xE5 *
// ------------------------------
bitfield LFN_Sequence {
padding : 1;
IS_LAST_ENTRY : 1 [[name("IS_LAST_ENTRY: [0=NO | 1=YES] ==")]];
padding : 1;
LFN_SEQ_NUM : 5;
} [[bitfield_order(
std::core::BitfieldOrder::MostToLeastSignificant, 8)]];
// ------------------------------
// DIRECTORY ENTRY STATUS/SEQUENCE HELPERS
// ------------------------------
enum Entry_Status : u8 {
EMPTY_ENTRY = 0x00,
DOT_ENTRY = 0x2E,
DELETED_ENTRY = 0xE5,
ACTIVE_1ST_ENTRY = 0x01,
ACTIVE_2ND_ENTRY = 0x02,
ACTIVE_3RD_ENTRY = 0x03,
ACTIVE_4TH_ENTRY = 0x04,
ACTIVE_5TH_ENTRY = 0x05,
ACTIVE_6TH_ENTRY = 0x06,
ACTIVE_7TH_ENTRY = 0x07,
ACTIVE_8TH_ENTRY = 0x08,
ACTIVE_9TH_ENTRY = 0x09,
ACTIVE_10TH_ENTRY = 0x0A,
ACTIVE_11TH_ENTRY = 0x0B,
ACTIVE_12TH_ENTRY = 0x0C,
ACTIVE_13TH_ENTRY = 0x0D,
ACTIVE_14TH_ENTRY = 0x0E,
ACTIVE_15TH_ENTRY = 0x0F,
ACTIVE_16TH_ENTRY = 0x10,
ACTIVE_17TH_ENTRY = 0x11,
ACTIVE_18TH_ENTRY = 0x12,
ACTIVE_19TH_ENTRY = 0x13,
ACTIVE_20TH_ENTRY = 0x14,
ACTIVE_1ST_ENTRY_LAST = 0x41,
ACTIVE_2ND_ENTRY_LAST = 0x42,
ACTIVE_3RD_ENTRY_LAST = 0x43,
ACTIVE_4TH_ENTRY_LAST = 0x44,
ACTIVE_5TH_ENTRY_LAST = 0x45,
ACTIVE_6TH_ENTRY_LAST = 0x46,
ACTIVE_7TH_ENTRY_LAST = 0x47,
ACTIVE_8TH_ENTRY_LAST = 0x48,
ACTIVE_9TH_ENTRY_LAST = 0x49,
ACTIVE_10TH_ENTRY_LAST = 0x4A,
ACTIVE_11TH_ENTRY_LAST = 0x4B,
ACTIVE_12TH_ENTRY_LAST = 0x4C,
ACTIVE_13TH_ENTRY_LAST = 0x4D,
ACTIVE_14TH_ENTRY_LAST = 0x4E,
ACTIVE_15TH_ENTRY_LAST = 0x4F,
ACTIVE_16TH_ENTRY_LAST = 0x50,
ACTIVE_17TH_ENTRY_LAST = 0x51,
ACTIVE_18TH_ENTRY_LAST = 0x52,
ACTIVE_19TH_ENTRY_LAST = 0x53,
ACTIVE_20TH_ENTRY_LAST = 0x54,
};
// ------------------------------
// HELPER FOR LFN FIRST BYTE
// ------------------------------
union LFNEntry_FirstByte {
Entry_Status status;
LFN_Sequence seq_num;
};
// ------------------------------
// SFN ATTRIBUTE HELPER
// ------------------------------
bitfield Attributes {
readOnly : 1;
hidden : 1;
systemFile : 1;
volumeLabel : 1;
subDirectory : 1;
archive : 1;
padding : 2;
} [[bitfield_order(
std::core::BitfieldOrder::LeastToMostSignificant, 8)]];
// ------------------------------
// ROOT DIRECTORY ENTRY FUNC
// ------------------------------
fn dir_entry_marker(u64 abs_off) {
u8 first @ abs_off;
return first;
};
// ------------------------------
// ROOT DIRECTORY ENTRY FUNC
// ------------------------------
fn dir_entry_attr(u64 abs_off) {
u8 attr @ abs_off + 0x0B;
return attr;
};
// ------------------------------
// DATES AND TIMES FUNC
// ------------------------------
fn format_dos_time_field(std::time::DOSTime t) {
return std::time::format_dos_time(t, "{:02}:{:02}:{:02}");
};
fn format_dos_date_field(std::time::DOSDate d) {
return std::time::format_dos_date(d, "{1:02}-{0:02}-{2:04}");
};
// ------------------------------
// SHORT FILE NAME ALIAS PARSER
// ------------------------------
struct SFN_Entry_Alias {
char fileName[8] [[name("SFN"), comment("Short File Name (8dot3)")]];
char extension[3] [[name("EXT"), comment("File Extension (8dot3)")]];
Attributes attributes [[name("RASH ATTR"), comment("Read-Only | Archive | System | Hidden | SubDir...")]];
u8 reserved [[comment("Zeros")]];
u8 milliseconds [[comment("Add to Times for Refinement")]];
std::time::DOSTime Created_Time [[format("format_dos_time_field")]];
std::time::DOSDate Created_Date [[format("format_dos_date_field")]];
std::time::DOSDate Accessed_Date [[format("format_dos_date_field")]];
u16 Cluster_Hi [[comment("High Cluster if Needed")]];
std::time::DOSTime Modified_Time [[format("format_dos_time_field")]];
std::time::DOSDate Modified_Date [[format("format_dos_date_field")]];
u16 Cluster_Lo [[comment("Starting Cluster or Combine with Cluster_Hi")]];
u32 fileSize [[name("FILE_SIZE"), comment("File Size in Bytes")]];
u32 first_cluster = (Cluster_Hi << 16) | Cluster_Lo;
u8 FILE_DATA[fileSize] @ dataRegionStart + (first_cluster -2) * bytesPerCluster [[comment("Pointer to the Files Content")]];
sfn_count += 1;
if (fileName[0] == 0xE5) {
sfn_del_count += 1;
}
};
// ------------------------------
// LOOOONG FILE NAME PARSER
// ------------------------------
struct LFN_Entry {
u64 curr_first_byte = $;
u8 curr_attr = dir_entry_attr(curr_first_byte);
LFNEntry_FirstByte SeqByte [[name("SEQUENCE_NUM"), comment("0x01-0x20 | Add 0x40 to Last LFN Entry")]];
char16 NAME_1[5] [[comment("First 5 Characters of LFN")]];
Attributes attributes [[name("LFN_ATTR"), comment("0x0F = LFN")]];
padding[1] [[comment("Zeros")]];
u8 nameChecksum [[name("Checksum"), comment("Checksum Calculated on SFN_ALIAS")]];
char16 NAME_2[6] [[comment("Next 6 Characters of LFN")]];
padding[2] [[comment("Zeros")]];
char16 NAME_3[2] [[comment("Next 2 Characters of LFN")]];
// ATTEMPT TO CLEANUP UNICODE LFN... DOES NOT ACCOUNT FOR MULTI LFN ENTRIES
if (curr_attr == 0x0F) {
char display_name[32] @ $ - 32 [[
name(
(NAME_1[0] >= 0x20 && NAME_1[0] <= 0x7E ? std::string::to_string(NAME_1[0]) : "") +
(NAME_1[1] >= 0x20 && NAME_1[1] <= 0x7E ? std::string::to_string(NAME_1[1]) : "") +
(NAME_1[2] >= 0x20 && NAME_1[2] <= 0x7E ? std::string::to_string(NAME_1[2]) : "") +
(NAME_1[3] >= 0x20 && NAME_1[3] <= 0x7E ? std::string::to_string(NAME_1[3]) : "") +
(NAME_1[4] >= 0x20 && NAME_1[4] <= 0x7E ? std::string::to_string(NAME_1[4]) : "") +
(NAME_2[0] >= 0x20 && NAME_2[0] <= 0x7E ? std::string::to_string(NAME_2[0]) : "") +
(NAME_2[1] >= 0x20 && NAME_2[1] <= 0x7E ? std::string::to_string(NAME_2[1]) : "") +
(NAME_2[2] >= 0x20 && NAME_2[2] <= 0x7E ? std::string::to_string(NAME_2[2]) : "") +
(NAME_2[3] >= 0x20 && NAME_2[3] <= 0x7E ? std::string::to_string(NAME_2[3]) : "") +
(NAME_2[4] >= 0x20 && NAME_2[4] <= 0x7E ? std::string::to_string(NAME_2[4]) : "") +
(NAME_2[5] >= 0x20 && NAME_2[5] <= 0x7E ? std::string::to_string(NAME_2[5]) : "") +
(NAME_3[0] >= 0x20 && NAME_3[0] <= 0x7E ? std::string::to_string(NAME_3[0]) : "") +
(NAME_3[1] >= 0x20 && NAME_3[1] <= 0x7E ? std::string::to_string(NAME_3[1]) : "")
)
]];
}
lfn_count += 1;
if (SeqByte.status == Entry_Status::DELETED_ENTRY) {
lfn_del_count += 1;
}
};
// ------------------------------
// SHORT FILE NAME PARSER
// ------------------------------
struct SFN_Entry {
char fileName[8] [[name("SFN"), comment("Short File Name (8dot3)")]];
char extension[3] [[name("EXT"), comment("File Extension (8dot3)")]];
Attributes attributes [[name("RASH ATTR"), comment("Read-Only | Archive | System | Hidden | SubDir...")]];
u8 reserved [[comment("Zeros")]];
u8 milliseconds [[comment("Add to Times for Refinement")]];
std::time::DOSTime Created_Time [[format("format_dos_time_field")]];
std::time::DOSDate Created_Date [[format("format_dos_date_field")]];
std::time::DOSDate Accessed_Date [[format("format_dos_date_field")]];
u16 Cluster_Hi [[comment("High Cluster if Needed")]];
std::time::DOSTime Modified_Time [[format("format_dos_time_field")]];
std::time::DOSDate Modified_Date [[format("format_dos_date_field")]];
u16 Cluster_Lo [[comment("Starting Cluster or Combine with Cluster_Hi")]];
u32 fileSize [[name("FILE_SIZE"), comment("File Size in Bytes")]];
u32 first_cluster = (Cluster_Hi << 16) | Cluster_Lo;
u8 FILE_DATA[fileSize] @ dataRegionStart + (first_cluster -2) * bytesPerCluster [[comment("Pointer to the File Content")]];
sfn_count += 1;
if (fileName[0] == 0xE5) {
sfn_del_count += 1;
}
};
// ------------------------------
// SUBDIRECTORY PARSER | LEVEL 2
// ------------------------------
struct SubDirParser {
u8 first = dir_entry_marker($);
u8 attr = dir_entry_attr($);
u64 next_first_byte = $ + 32;
u8 next_attr = dir_entry_attr(next_first_byte); // current offset plus 12 bytes (offset 0x0B of entry)
if (first != 0x00 && attr == 0x0F) {
LFN_Entry LFN_ENTRY;
if (next_first_byte != 0x00 && next_first_byte != 0xE5 && next_attr == 0x0F) {
LFN_Entry next_LFN_ENTRY;
SFN_Entry_Alias SFN_ALIAS;
}
} else if (first != 0x00 && attr != 0x0F) {
SFN_Entry SFN_ENTRY;
}
};
// ------------------------------
// ROOT DIRECTORY ENTRY PARSER
// ROUGH METHOD OF PARSING SFN/LFN/SFN_ALIAS/SUBDIR TWO LEVELS DEEP
// IF THE PATTERN CRASHES - THIS IS LIKELY WHY
// ------------------------------
struct RootDirParser {
u64 curr_first_byte = $;
u8 curr_attr = dir_entry_attr(curr_first_byte); // current offset plus 12 bytes (offset 0x0B of entry)
u64 next_first_byte = $ + 32;
u8 next_attr = dir_entry_attr(next_first_byte); // current offset plus 12 bytes (offset 0x0B of entry)
bool is_subdir = false;
if (curr_first_byte != 0x00 && curr_first_byte != 0xE5 && curr_attr == 0x0F) {
LFN_Entry LFN_ENTRY;
if (next_first_byte != 0x00 && next_first_byte != 0xE5 && next_attr == 0x0F) {
LFN_Entry next_LFN_ENTRY;
SFN_Entry_Alias SFN_ALIAS;
is_subdir = SFN_ALIAS.attributes.subDirectory;
if (SFN_ALIAS.attributes.subDirectory && next_first_byte != 0x00 && next_attr != 0xE5) {
is_subdir = SFN_ALIAS.attributes.subDirectory;
u64 dir_start_addr = dataRegionStart + (SFN_ALIAS.first_cluster - 2) * bytesPerCluster;
SubDirParser SUB_DIR_INDEX[while(std::mem::read_unsigned($, 1) != 0x00)] @ dir_start_addr;
}
}
if (next_first_byte != 0x00 && next_first_byte != 0xE5 && next_attr != 0x0F) {
SFN_Entry_Alias SFN_ALIAS;
is_subdir = SFN_ALIAS.attributes.subDirectory;
if (SFN_ALIAS.attributes.subDirectory && next_first_byte != 0x00 && next_attr != 0xE5) {
is_subdir = SFN_ALIAS.attributes.subDirectory;
u64 dir_start_addr = dataRegionStart + (SFN_ALIAS.first_cluster - 2) * bytesPerCluster;
SubDirParser SUB_DIR_INDEX[while(std::mem::read_unsigned($, 1) != 0x00)] @ dir_start_addr;
}
}
} else if (curr_first_byte != 0x00 && curr_first_byte != 0xE5 && curr_attr != 0x0F) {
SFN_Entry SFN_ENTRY;
is_subdir = SFN_ENTRY.attributes.subDirectory;
if (SFN_ENTRY.attributes.subDirectory && next_first_byte != 0x00 && next_attr != 0xE5) {
is_subdir = SFN_ENTRY.attributes.subDirectory;
u64 dir_start_addr = dataRegionStart + (SFN_ENTRY.first_cluster - 2) * bytesPerCluster;
SubDirParser SUB_DIR_INDEX[while(std::mem::read_unsigned($, 1) != 0x00)] @ dir_start_addr;
}
} else if (curr_first_byte != 0x00 && current_first_byte == 0xE5) {
if (next_first_byte != 0x00 && next_attr == 0x0F) {
LFN_Entry LFN_ENTRY;
if (next_first_byte != 0x00 && next_first_byte != 0xE5) {
if (next_attr != 0x0F) {
SFN_Entry_Alias SFN_ALIAS;
is_subdir = SFN_ALIAS.attributes.subDirectory;
if (next_first_byte != 0x00 && next_first_byte != 0xE5 && next_attr != 0x0F) {
SFN_Entry_Alias SFN_ALIAS2; // otherwise switch to SFN
is_subdir = SFN_ALIAS.attributes.subDirectory;
if (SFN_ALIAS.attributes.subDirectory && next_first_byte != 0x00 && next_attr != 0xE5) {
is_subdir = SFN_ALIAS.attributes.subDirectory;
u64 dir_start_addr = dataRegionStart + (SFN_ALIAS.first_cluster - 2) * bytesPerCluster;
SubDirParser SUB_DIR_INDEX[while(std::mem::read_unsigned($, 1) != 0x00)] @ dir_start_addr;
}
}
}
}
} else {
SFN_Entry SFN_ENTRY;
is_subdir = SFN_ENTRY.attributes.subDirectory;
}
}
} [[name(format_element($, start_index, is_subdir)), comment("FILE/DIR [INDX #]")]];
// ------------------------------
// NAME FORMATTER
// ------------------------------
fn format_element(auto v, u64 offset, bool subdir) {
if (subdir) {
return std::format("SUB_DIR [{:02}]", std::core::array_index() + offset);
} else {
return std::format("FILE [{:02}]", std::core::array_index() + offset);
}
};
// ------------------------------
// ROOT DIRECTORY HEADER PARSER
// ------------------------------
struct RootDirHeader {
char VolumeName[11] [[comment("User Defined Name of the VOL")]];
u8 VolumeLabelFlag [[comment("Indicates the Preceding VOL LABEL")]];
padding[10] [[comment("Zeros")]];
std::time::DOSTime Created_Time [[format("format_dos_time_field"), comment("Last Write Time - Typically when Created/Formatted, but NOT ALWAYS...(DISK IMAGE/FAT DRIVERS)")]];
std::time::DOSDate Created_Date [[format("format_dos_date_field"), comment("Last Write Date - Typically when Created/Formatted, but NOT ALWAYS...(DISK IMAGE/FAT DRIVERS)")]];
padding[6] [[comment("Zeros")]];
};
// ------------------------------
// VBR SIGNATURE HELPER
// ------------------------------
enum VBRSignature : u16 {
VBR_SIG = 0xAA55
};
// ------------------------------
// FILE SYSTEM INFO BLOCK
// ------------------------------
struct FSInfo {
u32 leadSignature [[comment("RRaA")]];
padding[480] [[comment("Zeros")]];
u32 structSignature [[comment("FSINFO Signature")]];
u32 freeClusterCount [[comment("Approximate Free Cluster Count")]];
u32 nextFreeCluster [[comment("FAT1: Suggested Starting Point")]];
padding[14] [[comment("Zeros")]];
VBRSignature VBR_SIG [[comment("0x55AA")]];
};
// ------------------------------
// FAT12/16/32 BIOS PARAMETER BLOCK (BPB)
// ------------------------------
struct BPB_Common {
u8 jmp_boot[3] [[comment("Assembly Instructions to Jump to Boot Code")]];
char oem_name[8] [[comment("MSDOS/BSD")]];
u16 bytes_per_sector [[comment("512,1024,2048,4096")]];
u8 sectors_per_cluster [[comment("Under 32K - Must be a power of 2")]];
u16 reserved_sectors [[comment("Size of Reserved Area in Sectors")]];
u8 num_fats [[comment("Typically 2, but can be 1 for Small Volumes")]];
u16 root_entry_count [[comment("Max Num of Entries -- 0 for FAT32| 512 for FAT16")]];
u16 total_sectors16 [[comment("if 0, use total_sectors32")]];
u8 media_type [[comment("0xF8=FIXED DISK | 0xF0=REMOVABLE")]];
u16 fat_size16 [[comment("Size of each FAT in Sectors for FAT12/16; 0 for FAT32")]];
u16 sectors_per_track [[comment("Legacy")]];
u16 num_heads [[comment("Legacy")]];
u32 hidden_sectors [[comment("Num of Sectors before the Volume")]];
u32 total_sectors32 [[comment("32bit Value of Total Num of Sectors in Volume")]];
// ----------------------vvv-----
// FAT32 EXTENDED
// ----------------------vvv-----
u32 FAT_Sector_Count [[comment("Total Sectors per FAT")]];
u16 ext_flags [[comment("16bit Value: BIT_7 = 1 == 1 FAT USED | Otherwise both FATs USED")]];
u16 fs_version [[comment("Major and Minor | None")]];
u32 root_cluster [[comment("Cluster Num of Root Dir")]];
u16 fs_info_sector [[comment("FS_INFO Location")]];
u16 backup_boot_sector [[comment("VBR Backup Location")]];
u8 reserved[12] [[comment("Zeros")]];
u8 drive_number [[comment("BIOS INT13h Drive Num")]];
u8 reserved1 [[comment("Zeros")]];
u8 boot_signature [[comment("Extended Boot Sig = 0x29")]];
u32 volume_id [[comment("Volume Serial Number - Based on Created Date/Time")]];
char volume_label[11] [[comment("No Name | User Defined Name | Check Root Dir")]];
char fs_type[8] [[comment("FAT32 ")]];
u8 bootstrap[420] [[comment("Until Signature")]];
VBRSignature VBR_SIG [[comment("0x55AA")]];
// ----------------------vvv-----
// UPDATE CONSTANTS/GLOBALS
// ----------------------vvv-----
bytesPerCluster = sectors_per_cluster * bytes_per_sector;
rootDirSectors = ((root_entry_count * 32) + (bytes_per_sector - 1)) / bytes_per_sector;
firstDataSector = reserved_sectors + (num_fats * FAT_Sector_Count) + rootDirSectors;
dataRegionStart = firstDataSector * bytes_per_sector;
};
// -----------------------------------------------------------------------------
// FAT32 MAIN RELATED
// -----------------------------------------------------------------------------
// V V V V V V V V V V
// -----------------------------------------------------------------------------
// ------------------------------
// FAT32 VOLUME BOOT RECORD
// ------------------------------
BPB_Common F32_VBR @ $;
VBR_OFFSET = F32_VBR.hidden_sectors * F32_VBR.bytes_per_sector;
/// ------------------------------
// FILE SYSTEM INFO BLOCK
// ------------------------------
FSInfo FS_INFO @ F32_VBR.fs_info_sector * F32_VBR.bytes_per_sector;
root_dir_start = dataRegionStart + ((F32_VBR.root_cluster - 2) * bytesPerCluster) + 32;
// ------------------------------
// FILE ALLOCATION TABLE
// *** HAS GLOBAL AT TOP ***
// ------------------------------
FAT1_start_offset = F32_VBR.reserved_sectors * F32_VBR.bytes_per_sector;
FAT2_start_offset = FAT1_start_offset + (F32_VBR.FAT_Sector_Count * F32_VBR.bytes_per_sector);
FAT_ClusterHeap_Count = F32_VBR.FAT_Sector_Count * F32_VBR.bytes_per_sector / CLUSTER_SIZE_BYTES;
FAT_Header FAT1_HEADER @ FAT1_start_offset;
FAT_Entry FAT1[MAX_FAT_CHUNKS] @ FAT1_start_offset + 8;
FAT_Header FAT2_HEADER @ FAT2_start_offset;
FAT_Entry FAT2[MAX_FAT_CHUNKS] @ FAT2_start_offset + 8;
// ------------------------------
// ROOT DIRECTORY HEADER
// ------------------------------
RootDirHeader ROOT_DIR_HEADER @ dataRegionStart + ((F32_VBR.root_cluster - 2) * bytesPerCluster);
// ----*-----*------*------*-----
// * * ROOT DIRECTORY PARSER * *
// ----*-----*------*------*-----
RootDirParser ROOT_DIR_ENTRIES[while(std::mem::read_unsigned($, 1) != 0x00)] @ root_dir_start;
// ------------------------------
// SFN <-> CLUSTER RELATION OVERLAY
// *** HAS GLOBAL AT TOP ***
// ------------------------------
INFO_Overlay SFN_CLUSTER_LIST[MAX_SFN_CLUSTER_RELATIONS] @ FAT1_start_offset [[name("SFN <-> CLUSTER (DERIVED)")]];
// ------------------------------
// FAT32 VOLUME REPORT
// *** HAS GLOBAL AT TOP ***
// ------------------------------
abs_FAT1_start_offset = VBR_OFFSET + (F32_VBR.reserved_sectors * F32_VBR.bytes_per_sector);
abs_FAT2_start_offset = abs_FAT1_start_offset + (F32_VBR.FAT_Sector_Count * F32_VBR.bytes_per_sector);
abs_rootDirStart_offset = VBR_OFFSET + dataRegionStart;
if (VOLUME_REPORT) {
std::print(" ");
std::print("-----------------------------------------");
std::print("---------- FAT32 VOLUME_REPORT ----------");
std::print("-----------------------------------------");
std::print("VOL_LABEL = {}", F32_VBR.volume_label);
std::print("FILE_SYSTEM = {}", F32_VBR.fs_type);
std::print("SERIAL_NUMBER = 0x{:X}", F32_VBR.volume_id);
std::print("-----------------------------------------");
std::print("BYTES/SECTOR = {:02}", F32_VBR.bytes_per_sector);
std::print("SECTORS/CLUSTER = {:02}", F32_VBR.sectors_per_cluster);
std::print("BYTES/CLUSTER = {:02}", bytesPerCluster);
std::print("ROOT_ENTRIES = {:02}", F32_VBR.root_entry_count);
std::print("CLUSTER_COUNT = {:02}", (F32_VBR.total_sectors32 - firstDataSector) / F32_VBR.sectors_per_cluster);
std::print("-----------------------------------------");
std::print("VOLUME_SIZE = {:02} SECTORS", F32_VBR.total_sectors32);
std::print("VOLUME_SIZE = {:.4f} GB @ 1000", (F32_VBR.total_sectors32 * F32_VBR.bytes_per_sector) / 1000.0 / 1000.0 / 1000.0);
std::print("VOLUME_SIZE = {:.4f} GiB @ 1024", (F32_VBR.total_sectors32 * F32_VBR.bytes_per_sector) / 1024.0 / 1024.0 / 1024.0);
std::print("-----------------------------------------");
std::print("RESERVED_SECTORS = {:02}", F32_VBR.reserved_sectors);
std::print("FAT_COUNT = {:02}", F32_VBR.num_fats);
std::print("FAT_SIZE = {:02} SECTORS", F32_VBR.FAT_Sector_Count);
std::print("FAT1_START_OFF = {} | 0x{:02X}", abs_FAT1_start_offset, abs_FAT1_start_offset);
std::print("FAT2_START_OFF = {} | 0x{:02X}", abs_FAT2_start_offset, abs_FAT2_start_offset);
std::print("ROOT_DIR_CLUSTER = {:02}", F32_VBR.root_cluster);
std::print("ROOT_DIR_OFFSET = {} | 0x{:02X}", abs_rootDirStart_offset, abs_rootDirStart_offset);
std::print("-----------------------------------------");
if (sfn_del_count >= 1) {
std::print("SFN_DEL(xE5) = DETECTED");
}
if (lfn_del_count >= 1) {
std::print("LFN_DEL(xE5) = DETECTED");
}
std::print("FAT1_EOF_COUNT = {:02}", allocated_file_count / 2); // divided by 2 (FAT1/FAT2)
std::print("-----------------------------------------");
std::print("------------------ END ------------------");
std::print("-----------------------------------------");
std::print(" ");
}

1571
patterns/DFIR/NTFS.hexpat Normal file

File diff suppressed because it is too large Load Diff

616
patterns/DFIR/exFAT.hexpat Normal file
View File

@@ -0,0 +1,616 @@
#pragma author Formula Zero One Technologies
#pragma description exFAT Filesystem (exFAT_v2.0)
#pragma MIME application/x-ima
#pragma endian little
// -----------------------------------------------------------------------------
// CREDIT
// -----------------------------------------------------------------------------
// Based on /fs/exfat.hexpat by WerWolv
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// NOTES
// -----------------------------------------------------------------------------
// Imported by DISK_PARSER.hexpat
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// TODO
// -----------------------------------------------------------------------------
// Recursive parsing of Root Directory / SubDirs
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// IMPORTS
// -----------------------------------------------------------------------------
import std.core;
import std.io;
import std.time;
import std.mem;
import type.guid;
import type.magic;
import type.base;
// ------------------------------
// FORWARD DECS/GLOBALS
// ------------------------------
// *** ATTENTION ***
// SET MAXIMUM NUMBER OF 4 BYTE CHUNKS TO PARSE FROM FAT1
// SET MAXIMUM NUMBER OF DIRECTORY ENTRIES TO PARSE FROM ROOT DIRECTORY
// DEFAULTS ARE 4096 | 2500
// Choose a value greater than 1 and less than 65536 OR increase the Array size limit with "#define... "
// -------**************---vvvv--- |
const u64 MAX_FAT_CHUNKS = 4096;
// -------**************---^^^^--- |
// -------**************---vvvv--- |
const u64 MAX_DIR_ENTRIES = 2500;
// -------**************---^^^^--- |
// *** ATTENTION ***
// ---*******---*******----vvvv--- |
const bool VOLUME_REPORT = true;
// ---*******---*******----^^^^--- |
u64 allocated_file_count;
u64 rdc;
// --------------------------
// exFAT DIRECTORY ENTRY HELPER
// --------------------------
enum EntryType : u8 {
UNUSED_ENTRY = 0x00,
ACTIVE_VOLUME_GUID_ENTRY = 0xA0,
INACTIVE_VOLUME_GUID_ENTRY = 0x20,
ACTIVE_TEXFAT_ENTRY = 0xA1,
INACTIVE_TEXFAT_ENTRY = 0x21,
ACTIVE_ACCESS_CONTROL_ENTRY = 0xA2,
INACTIVE_ACCESS_CONTROL_ENTRY = 0x22,
ACTIVE_VOLUME_LABEL_ENTRY = 0x83,
INACTIVE_VOLUME_LABEL_ENTRY = 0x03,
ACTIVE_ALLOCATION_BITMAP_ENTRY = 0x81,
INACTIVE_ALLOCATION_BITMAP_ENTRY = 0x01,
ACTIVE_UPCASE_TABLE_ENTRY = 0x82,
INACTIVE_UPCASE_TABLE_ENTRY = 0x02,
ACTIVE_FILE_INFO_ENTRY = 0x85,
INACTIVE_FILE_INFO_ENTRY = 0x05,
ACTIVE_STREAM_ENTRY = 0xC0,
INACTIVE_STREAM_ENTRY = 0x40,
ACTIVE_FILENAME_ENTRY = 0xC1,
INACTIVE_FILENAME_ENTRY = 0x41,
};
// ------------------------------
// DATES AND TIMES FUNC
// ------------------------------
fn format_dos_time_field(std::time::DOSTime t) {
return std::time::format_dos_time(t, "{:02}:{:02}:{:02}");
};
fn format_dos_date_field(std::time::DOSDate d) {
return std::time::format_dos_date(d, "{1:02}-{0:02}-{2:04}");
};
// ------------------------------
// BITFIELD HELPERS
// ------------------------------
bitfield Entry_Flags {
unsigned TypeCode : 5;
unsigned Importance : 1;
unsigned Category : 1;
unsigned InUse : 1;
} [[bitfield_order(std::core::BitfieldOrder::LeastToMostSignificant, 8)]];
bitfield Bitmap_Flags {
unsigned Bitmap_1 : 1;
unsigned Bitmap_2 : 1;
} [[bitfield_order(std::core::BitfieldOrder::LeastToMostSignificant, 8)]];
bitfield General_Primary_Flags {
unsigned Allocation_Possible : 1;
unsigned No_FAT_Chain : 1;
unsigned Reserved : 6;
} [[bitfield_order(std::core::BitfieldOrder::LeastToMostSignificant, 8)]];
bitfield General_Secondary_Flags {
unsigned Allocation_Possible : 1;
unsigned No_FAT_Chain : 1;
unsigned Reserved : 6;
} [[bitfield_order(std::core::BitfieldOrder::LeastToMostSignificant, 8)]];
bitfield File_Attr_Flags {
unsigned Read_Only : 1;
unsigned Hidden : 1;
unsigned System : 1;
unsigned Directory : 1;
unsigned Archive : 1;
Reserved : 11;
} [[bitfield_order(std::core::BitfieldOrder::LeastToMostSignificant, 16)]];
// --------------------------
// exFAT DIRECTORY ENTRY STRUCTURES
// --------------------------
// xA0 / x20 = Volume GUID Entry
struct VolumeGUID_Entry {
Entry_Flags EntryFlags [[comment("ENTRY TYPE IDENTIFIER")]];
u8 SecondaryCount[3] [[comment("COUNT OF SUBSEQUENT ENTRIES")]];
type::Hex<u16> SetChecksum [[comment("16bit CHECKSUM")]];
General_Primary_Flags PrimaryFlags;
type::GUID GUID;
u8 Reserved_1[9];
};
// xA1 / x21 = TexFAT / Padding Entry
struct TexFATPadding_Entry {
Entry_Flags Flags [[comment("ENTRY TYPE IDENTIFIER")]];
u8 Reserved_1[31];
};
// xA2 / x22 = Access Control Entry
struct AccessControl_Entry {
Entry_Flags Flags [[comment("ENTRY TYPE IDENTIFIER")]];
u8 Reserved[31];
};
// x83 / x03 = Volume Label Entry
struct VolumeLabel_Entry {
Entry_Flags Flags [[comment("ENTRY TYPE IDENTIFIER")]];
u8 LabelLength [[comment("NUMBER OF UTF-16 CHARACTERS")]];
char16 Label[LabelLength] [[comment("VOLUME LABEL: UTF-16")]];
u8 Reserved[32-2-(LabelLength * 2)];
};
// x81 / x01 = Allocation Bitmap Entry
struct AllocationBitmap_Entry {
Entry_Flags Flags [[comment("ENTRY TYPE IDENTIFIER")]];
Bitmap_Flags BitmapFlags;
u8 Reserved_1[18];
u32 FirstCluster [[comment("FIRST LOGICAL CLUSTER NUMBER")]];
u64 DataLength [[comment("DATA SIZE")]];
u8 FILE_DATA[DataLength] @ temp_root_location + (FirstCluster - exFAT_VBR.root_dir_cluster) * bytesPerCluster [[comment("POINTER TO THE CLUSTER CONTENT")]];
};
// x82 / x02 = UpCase Table Entry
struct UpCaseTable_Entry {
Entry_Flags Flags [[comment("ENTRY TYPE IDENTIFIER")]];
u8 Reserved_1[3];
type::Hex<u32> TableChecksum [[comment("16bit CHECKSUM")]];
u8 Reserved_2[12];
u32 FirstCluster [[comment("FIRST LOGICAL CLUSTER NUMBER")]];
u64 DataLength [[comment("DATA SIZE")]];
u8 FILE_DATA[DataLength] @ temp_root_location + (FirstCluster - exFAT_VBR.root_dir_cluster) * bytesPerCluster [[comment("POINTER TO THE CLUSTER CONTENT")]];
};
// x85 / x05 = File Info Entry
struct FileInfo_Entry {
Entry_Flags EntryFlags [[comment("ENTRY TYPE IDENTIFIER")]];
u8 SecondaryCount [[comment("COUNT OF SUBSEQUENT ENTRIES")]];
type::Hex<u16> SetChecksum [[comment("16bit CHECKSUM")]];
File_Attr_Flags AttrFlags [[comment("FILE ATTRS: RASH")]];
u16 Reserved_1;
std::time::DOSTime Created_Time [[format("format_dos_time_field")]];
std::time::DOSDate Created_Date [[format("format_dos_date_field")]];
std::time::DOSTime Accessed_Time [[format("format_dos_time_field")]];
std::time::DOSDate Accessed_Date [[format("format_dos_date_field")]];
std::time::DOSTime Modified_Time [[format("format_dos_time_field")]];
std::time::DOSDate Modified_Date [[format("format_dos_date_field")]];
u8 Created_10ms_Increments [[comment("Add to Times for Refinement")]];
u8 Modified_10ms_Increments [[comment("Add to Times for Refinement")]];
s8 Created_UTC_Diff [[comment("Add to Times for Refinement")]];
s8 Modified_UTC_Diff [[comment("Add to Times for Refinement")]];
s8 Accessed_UTC_Diff [[comment("Add to Times for Refinement")]];
u8 Reserved[7];
};
// xC1 / x41 = File Name Entry
struct FileName_Entry {
Entry_Flags EntryFlags [[comment("ENTRY TYPE IDENTIFIER")]];
General_Secondary_Flags SecondaryFlags [[comment("COUNT OF SUBSEQUENT ENTRIES")]];
char16 FileName[15] [[comment("FILE NAME: UTF-16")]];
};
// xC0 / x40 = Stream Extension Entry
struct Stream_Entry {
Entry_Flags EntryFlags [[comment("ENTRY TYPE IDENTIFIER")]];;
General_Secondary_Flags SecondaryFlags [[comment("COUNT OF SUBSEQUENT ENTRIES")]];
u8 Reserved_1;
u8 NameLength [[comment("STREAM LENGTH")]];
type::Hex<u16> NameHash [[comment("16bit QUICK HASH: USED FOR FILE SEARCHING")]];
u16 Reserved_2;
u64 InitSize [[comment("INITIALIZED SIZE")]];
u32 Reserved_3;
u32 FirstCluster [[comment("FIRST LOGICAL CLUSTER NUMBER")]];
u64 ActualSize [[comment("PHYSICAL DATA SIZE")]];;
u8 FILE_DATA[InitSize] @ temp_root_location + (FirstCluster - exFAT_VBR.root_dir_cluster) * bytesPerCluster [[comment("POINTER TO THE CLUSTER CONTENT")]];
};
// --------------------------
// exFAT ROOT DIRECTORY
// --------------------------
struct RootDir {
EntryType Type;
padding[31];
match (Type) {
(EntryType::UNUSED_ENTRY): {
continue;
}
(EntryType::ACTIVE_VOLUME_GUID_ENTRY | EntryType::INACTIVE_VOLUME_GUID_ENTRY):{
VolumeGUID_Entry VOLUME_GUID_ENTRY @ $ - 32;
}
(EntryType::ACTIVE_TEXFAT_ENTRY | EntryType::INACTIVE_TEXFAT_ENTRY):{
TexFATPadding_Entry TEXFAT_PADDING_ENTRY @ $ - 32;
}
(EntryType::ACTIVE_ACCESS_CONTROL_ENTRY | EntryType::INACTIVE_ACCESS_CONTROL_ENTRY):{
AccessControl_Entry ACCESS_CONTROL_ENTRY @ $ - 32;
}
(EntryType::ACTIVE_VOLUME_LABEL_ENTRY | EntryType::INACTIVE_VOLUME_LABEL_ENTRY):{
VolumeLabel_Entry VOLUME_LABEL_ENTRY @ $ - 32;
}
(EntryType::ACTIVE_ALLOCATION_BITMAP_ENTRY | EntryType::INACTIVE_ALLOCATION_BITMAP_ENTRY):{
AllocationBitmap_Entry ALLOCATION_BITMAP_ENTRY @ $ - 32;
u64 bitmap_cluster = ALLOCATION_BITMAP_ENTRY.FirstCluster;
char dolla_BITMAP_label[4] @ FAT1_start_offset + (bitmap_cluster * 4) [[name(std::format("$Bitmap"))]];
}
(EntryType::ACTIVE_UPCASE_TABLE_ENTRY | EntryType::INACTIVE_UPCASE_TABLE_ENTRY):{
UpCaseTable_Entry UPCASE_TABLE_ENTRY @ $ - 32;
u64 upcase_cluster = UPCASE_TABLE_ENTRY.FirstCluster;
char dolla_UPCASE_label[4] @ FAT1_start_offset + (upcase_cluster * 4) [[name(std::format("$UpCase"))]];
}
(EntryType::ACTIVE_FILE_INFO_ENTRY | EntryType::INACTIVE_FILE_INFO_ENTRY):{
FileInfo_Entry FILE_INFO_ENTRY @ $ - 32;
}
(EntryType::ACTIVE_STREAM_ENTRY | EntryType::INACTIVE_STREAM_ENTRY):{
Stream_Entry STREAM_EXT_ENTRY @ $ - 32;
}
(EntryType::ACTIVE_FILENAME_ENTRY | EntryType::INACTIVE_FILENAME_ENTRY):{
FileName_Entry FILE_NAME_ENTRY @ $ - 32;
}
}
} [[name(format_entry_name(std::mem::read_unsigned($-32, 1), std::core::array_index()))]];
fn format_entry_name(auto entry_type, u64 idx) {
return std::format("{}[{}]", type_name(entry_type), idx);
};
// ------------------------------
// TYPE RE-NAMER
// ------------------------------
fn type_name(u32 type) {
if (type == EntryType::UNUSED_ENTRY) return "UNUSED_ENTRY";
if (type == EntryType::ACTIVE_VOLUME_GUID_ENTRY) return "ACTIVE_VOLUME_GUID_ENTRY";
if (type == EntryType::INACTIVE_VOLUME_GUID_ENTRY) return "INACTIVE_VOLUME_GUID_ENTRY";
if (type == EntryType::ACTIVE_TEXFAT_ENTRY) return "ACTIVE_TEXFAT_ENTRY";
if (type == EntryType::INACTIVE_TEXFAT_ENTRY) return "INACTIVE_TEXFAT_ENTRY";
if (type == EntryType::ACTIVE_ACCESS_CONTROL_ENTRY) return "ACTIVE_ACCESS_CONTROL_ENTRY";
if (type == EntryType::INACTIVE_ACCESS_CONTROL_ENTRY) return "INACTIVE_ACCESS_CONTROL_ENTRY";
if (type == EntryType::ACTIVE_VOLUME_LABEL_ENTRY) return "ACTIVE_VOLUME_LABEL_ENTRY";
if (type == EntryType::INACTIVE_VOLUME_LABEL_ENTRY) return "INACTIVE_VOLUME_LABEL_ENTRY";
if (type == EntryType::ACTIVE_ALLOCATION_BITMAP_ENTRY) return "ACTIVE_ALLOCATION_BITMAP_ENTRY";
if (type == EntryType::INACTIVE_ALLOCATION_BITMAP_ENTRY) return "INACTIVE_ALLOCATION_BITMAP_ENTRY";
if (type == EntryType::ACTIVE_UPCASE_TABLE_ENTRY) return "ACTIVE_UPCASE_TABLE_ENTRY";
if (type == EntryType::INACTIVE_UPCASE_TABLE_ENTRY) return "INACTIVE_UPCASE_TABLE_ENTRY";
if (type == EntryType::ACTIVE_FILE_INFO_ENTRY) return "ACTIVE_FILE_INFO_ENTRY";
if (type == EntryType::INACTIVE_FILE_INFO_ENTRY) return "INACTIVE_FILE_INFO_ENTRY";
if (type == EntryType::ACTIVE_STREAM_ENTRY) return "ACTIVE_STREAM_ENTRY";
if (type == EntryType::INACTIVE_STREAM_ENTRY) return "INACTIVE_STREAM_ENTRY";
if (type == EntryType::ACTIVE_FILENAME_ENTRY) return "ACTIVE_FILENAME_ENTRY";
if (type == EntryType::INACTIVE_FILENAME_ENTRY) return "INACTIVE_FILENAME_ENTRY";
return "UNKNOWN";
};
// -----------------------------------------------------------------------------
// exFAT FILE ALLOCATION TABLE (FAT1) PARSER
// -----------------------------------------------------------------------------
const u32 CLUSTER_SIZE_BYTES = 4; // Each FAT32 entry = 4 bytes
const u32 FAT_EOF = 0x0FFFFFFF; // End-of-file marker
const u32 FAT_BAD = 0x0FFFFFF7; // Bad cluster marker
const u32 FIRST_ALLOC_CLUSTER = 2; // First usable cluster after reserved
enum FAT_Flags : u32 {
UNALLOCATED = 0x00000000,
END_OF_FILE = 0xFFFFFFFF, // L.END
BAD_CLUSTER = 0xFFFFFFF7, // L.END
};
union FAT_Union {
u32 DECIMAL [[hidden]];
FAT_Flags FAT_FLAG;
};
// ------------------------------
// Helper function for pointer label
// ------------------------------
fn cluster_label(u32 val) {
if (val == FAT_Flags::UNALLOCATED)
return "UNALLOCATED";
if (val == FAT_Flags::BAD_CLUSTER)
return "BAD";
if (val >= 0x0FFFFFF8)
return "EOF";
return std::format("{}", val);
};
// ------------------------------
// FAT1 HEAPS/CHAINS
// ------------------------------
struct FAT_Entry {
FAT_Union FAT [[inline]];
u32 cluster_num = (FIRST_ALLOC_CLUSTER) + (std::core::array_index());
u32 next_cluster = FAT.DECIMAL & 0x0FFFFFFF;
char hover_label[4] @ $ - 4 [[
name(std::format(
"Cluster: {} → {}",
cluster_num,
cluster_label(next_cluster)
))
]];
bool is_eof = next_cluster >= 0x0FFFFFF8;
bool is_bad = next_cluster == FAT_BAD;
bool is_free = next_cluster == 0;
if (is_eof) {
allocated_file_count += 1;
}
} [[name(format_fat_entry(FAT.DECIMAL, std::core::array_index(), FIRST_ALLOC_CLUSTER))]];
// ------------------------------
// FAT FORMATTER FUNC
// ------------------------------
fn format_fat_entry(u32 raw_value, u32 cluster_index, u32 first_alloc_cluster) {
u32 next_cluster = raw_value & 0x0FFFFFFF;
str next_label;
if (next_cluster == 0)
next_label = "UNALLOCATED";
else if (next_cluster == FAT_BAD)
next_label = "BAD";
else if (next_cluster == 0x0FFFFFFF)
next_label = "EOF";
else
next_label = std::format("{}", next_cluster);
u32 logical_cluster = first_alloc_cluster + cluster_index;
if (next_label == "UNALLOCATED" || next_label == "BAD" || next_label == "EOF")
return std::format("Cluster {}: {}", logical_cluster, next_label);
else
return std::format("Cluster {} → {}", logical_cluster, next_label);
};
// ------------------------------
// MEDIA DESCRIPTOR HELPER
// ------------------------------
enum Media_Descriptor : u8 {
SINGLE_SIDE_FLOPPY = 0xF0,
DOUBLE_SIDE_FLOPPY = 0xF9,
HARD_DISK_DRIVE = 0xF8,
};
// ------------------------------
// FAT HEADER PARSER
// ------------------------------
struct FAT_Header {
Media_Descriptor mediaDescriptor [[comment("0xF8=FIXED DISK | 0xF0=REMOVABLE")]];;
u8 exFAT_FAT_HEADER[7] [[comment("8 BYTES TOTAL: 4 BYTES REPRESENT PSUEDO CLUSTER 0 (SYSTEM) | 4 BYTES REPRESENT PSUEDO CLUSTER 1 (SYSTEM)(EOF)")]];
//Bitmap and UpCase overlays handled in RootDir parser
char root_dir_label[4] @ $ + ((rdc - 2) * 4) [[
name(std::format(
"ROOT_DIRECTORY"
))
]];
};
// ------------------------------
//SIGNATURE HELPER
// ------------------------------
enum VBRSignature : u16 {
VBR_SIG = 0xAA55
};
// ------------------------------
// EXTENDED BOOT REGION
// ------------------------------
struct ExtendedBoot {
u8 Extended_Boot_Sector[1 * bytesPerSector];
VBRSignature VBR_SIG @ $ - 2;
};
// ------------------------------
// BOOT SECTOR BITFIELD FLAGS
// ------------------------------
bitfield VolumeFlags {
unsigned Active : 1;
unsigned VolumeDirty : 1;
unsigned Media_Failure : 1;
unsigned Clear_to_Zero : 1;
Rserved : 12;
} [[bitfield_order(std::core::BitfieldOrder::LeastToMostSignificant, 16)]];
// ------------------------------
// EXFAT VOLUME BOOT RECORD
// ------------------------------
struct exFAT_BootSector {
u8 jmp_boot[3];
char fs_name[8]; // "EXFAT "
u8 must_be_zero[53];
u64 partition_offset; // in sectors
u64 volume_length; // in sectors
u32 fat_offset; // in sectors
u32 fat_length; // in sectors
u32 cluster_heap_offset; // in sectors
u32 cluster_count;
u32 root_dir_cluster;
u32 volume_serial;
u16 fs_revision;
VolumeFlags volume_flags;
u8 bytes_per_sector_shift; // 2^n
u8 sectors_per_cluster_shift; // 2^n
u8 number_of_fats;
u8 drive_select;
u8 percent_in_use;
u8 reserved[7];
u8 bootstrap[390];
VBRSignature VBR_SIG; // 0x55AA
rdc = root_dir_cluster;
};
// -------------------------------------------------------------------------
// MAIN
// -------------------------------------------------------------------------
// V V V V V V V V V
// -------------------------------------------------------------------------
exFAT_BootSector exFAT_VBR @ 0x0;
// -------------------------------------------------------------------------
// DERIVED CONSTANTS
// -------------------------------------------------------------------------
// ============= SIZES ===================================================================
u32 bytesPerSector = 1 << exFAT_VBR.bytes_per_sector_shift;
u32 bytesPerCluster = bytesPerSector << exFAT_VBR.sectors_per_cluster_shift;
// ============= OFFSETS =================================================================
u64 volumeStartSector = exFAT_VBR.partition_offset;
u64 volumeStartOffset = volumeStartSector * bytesPerSector;
u64 volumeSize = exFAT_VBR.volume_length * bytesPerSector;
u64 FAT1_start_offset = exFAT_VBR.fat_offset * bytesPerSector;
//For printing absolute offset
u64 RootDir_Offset = (exFAT_VBR.cluster_heap_offset +
((exFAT_VBR.root_dir_cluster - 2) << exFAT_VBR.sectors_per_cluster_shift))
* bytesPerSector + volumeStartOffset;
// ============= CLUSTERS ================================================================
u32 clusterSize = bytesPerCluster;
u32 clusterCount = exFAT_VBR.cluster_count;
u64 clusterHeapOffset = exFAT_VBR.cluster_heap_offset * bytesPerSector;
// -------------------------------------------------------------------------
// SECONDARY
// -------------------------------------------------------------------------
// V V V V V V V V V
// -------------------------------------------------------------------------
// ============= USAGE ===================================================================
u8 percentInUse = exFAT_VBR.percent_in_use;
// ============= EBS =====================================================================
ExtendedBoot Extended_Boot_Sectors[8] @ $;
// ============= OEM =====================================================================
u8 OEM_Parameters[1 * bytesPerSector] @ $;
// ============= ER ======================================================================
u8 Extended_Reserved[1 * bytesPerSector] @ $;
// ============= BCS =====================================================================
u8 Boot_Checksum[1 * bytesPerSector] @ $;
// ============= BBS =====================================================================
exFAT_BootSector Backup_Boot_Sector @ $;
// ============= BEBS ====================================================================
ExtendedBoot Backup_Extended_Boot_Sectors[8] @ $;
// ============= BOEM ====================================================================
u8 Backup_OEM_Parameters[1 * bytesPerSector] @ $;
// ============= BER =====================================================================
u8 Backup_Extended_Reserved[1 * bytesPerSector] @ $;
// ============= BBCS ====================================================================
u8 Backup_Boot_Checksum[1 * bytesPerSector] @ $;
// ============= FAT =====================================================================
// *** HAS GLOBAL AT TOP ***
FAT_Header FAT1_HEADER @ FAT1_start_offset;
FAT_Entry FAT1[MAX_FAT_CHUNKS] @ FAT1_start_offset + 8;
// ============= ROOT ====================================================================
// ROOT DIRECTORY
// *** HAS GLOBAL AT TOP ***
// for locating root directory within memory
u64 temp_root_location = (exFAT_VBR.root_dir_cluster - 2) * clusterSize + clusterHeapOffset;
RootDir ROOT_DIRECTORY[MAX_DIR_ENTRIES] @ temp_root_location;
// ============= REPORT ==================================================================
// VOLUME REPORT
// *** HAS GLOBAL AT TOP ***
if (VOLUME_REPORT) {
std::print(" ");
std::print("-----------------------------------------");
std::print("---------- EXFAT VOLUME_REPORT ----------");
std::print("-----------------------------------------");
std::print("FILE_SYSTEM = {}", exFAT_VBR.fs_name);
std::print("SERIAL_NUMBER = 0x{:X}", exFAT_VBR.volume_serial);
std::print("FS_REVISION = {}.{}", (exFAT_VBR.fs_revision >> 8) & 0xFF, exFAT_VBR.fs_revision & 0xFF);
bool _any = false;
if(exFAT_VBR.volume_flags.Active) {
std::print("FAT_FLAG = 0b{:X}", exFAT_VBR.volume_flags.Active);
_any = true;
}
if(exFAT_VBR.volume_flags.VolumeDirty) {
std::print("DIRTY_FLAG = 0b{:X}", exFAT_VBR.volume_flags.Volume_Dirty);
_any = true;
}
if(exFAT_VBR.volume_flags.Media_Failure) {
std::print("FAILURE_FLAG = 0b{:X}", exFAT_VBR.volume_flags.Media_Failure);
_any = true;
}
if(exFAT_VBR.volume_flags.Clear_to_Zero) {
std::print("CLEAR_TO_ZERO_FLAG = 0b{:X}", exFAT_VBR.volume_flags.Clear_to_Zero);
_any = true;
}
if (!_any){
std::print("VOL_FLAGS = NONE");
}
std::print("-----------------------------------------");
std::print("BYTES/SECTOR = {}", bytesPerSector);
std::print("SECTORS/CLUSTER = {}", 1 << exFAT_VBR.sectors_per_cluster_shift);
std::print("BYTES/CLUSTER = {}", bytesPerCluster);
std::print("CLUSTER_COUNT = {}", clusterCount);
std::print("-----------------------------------------");
std::print("VOLUME_SIZE = {} SECTORS", exFAT_VBR.volume_length);
std::print("VOLUME_SIZE = {:.4f} GB @ 1000", volumeSize / 1000.0 / 1000.0 / 1000.0);
std::print("VOLUME_SIZE = {:.4f} GiB @ 1024", volumeSize / 1024.0 / 1024.0 / 1024.0);
std::print("-----------------------------------------");
std::print("VOLUME_START_SEC = {}", volumeStartSector);
std::print("VOLUME_START_OFF = 0x{:X}", volumeStartOffset);
std::print("FAT1_START_OFF = 0x{:02X}", FAT1_start_offset);
std::print("CLUSTER_HEAP_OFF = 0x{:02X}", clusterHeapOffset);
std::print("ROOT_DIR_CLUSTER = {:02}", exFAT_VBR.root_dir_cluster);
std::print("ROOT_DIR_OFFSET = 0x{:02X}", RootDir_Offset);
std::print("-----------------------------------------");
std::print("PERCENT_IN_USE = {:02} %", percentInUse);
std::print("NUMBER_OF_FATS = {:02}", exFAT_VBR.number_of_fats);
std::print("DRIVE_SELECT = 0x{:02X}", exFAT_VBR.drive_select);
std::print("-----------------------------------------");
std::print("------------------ END ------------------");
std::print("-----------------------------------------");
std::print(" ");
}