mirror of
https://github.com/WerWolv/ImHex-Patterns.git
synced 2026-03-27 23:37:04 -05:00
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:
@@ -61,6 +61,7 @@ Everything will immediately show up in ImHex's Content Store and gets bundled wi
|
|||||||
| DDS | `image/vnd-ms.dds` | [`patterns/dds.hexpat`](patterns/dds.hexpat) | DirectDraw Surface |
|
| DDS | `image/vnd-ms.dds` | [`patterns/dds.hexpat`](patterns/dds.hexpat) | DirectDraw Surface |
|
||||||
| DEX | | [`patterns/dex.hexpat`](patterns/dex.hexpat) | Dalvik EXecutable Format |
|
| DEX | | [`patterns/dex.hexpat`](patterns/dex.hexpat) | Dalvik EXecutable Format |
|
||||||
| DICOM | `application/dicom` | [`patterns/dicom.hexpat`](patterns/dicom.hexpat) | DICOM image format |
|
| DICOM | `application/dicom` | [`patterns/dicom.hexpat`](patterns/dicom.hexpat) | DICOM image format |
|
||||||
|
| DISK_PARSER (DFIR) | `application/x-ima` | [`patterns/DFIR/DISK_PARSER.hexpat`](patterns/DFIR/DISK_PARSER.hexpat) | Recursive Disk/Volume/Filesystem parsing |
|
||||||
| DMG | | [`patterns/dmg.hexpat`](patterns/dmg.hexpat) | Apple Disk Image Trailer (DMG) |
|
| DMG | | [`patterns/dmg.hexpat`](patterns/dmg.hexpat) | Apple Disk Image Trailer (DMG) |
|
||||||
| DMP | | [`patterns/dmp64.hexpat`](patterns/dmp64.hexpat) | Windows Kernel Dump(DMP64) |
|
| DMP | | [`patterns/dmp64.hexpat`](patterns/dmp64.hexpat) | Windows Kernel Dump(DMP64) |
|
||||||
| DOS | `application/x-dosexec` | [`patterns/dos.hexpat`](patterns/dos.hexpat) | 16-bit real mode DOS EXE files |
|
| DOS | `application/x-dosexec` | [`patterns/dos.hexpat`](patterns/dos.hexpat) | 16-bit real mode DOS EXE files |
|
||||||
@@ -74,9 +75,11 @@ Everything will immediately show up in ImHex's Content Store and gets bundled wi
|
|||||||
| ESP32 Image | | [`patterns/esp32_image.hexpat`](patterns/esp32_image.hexpat) | Firmware image format for the ESP32 chip family |
|
| ESP32 Image | | [`patterns/esp32_image.hexpat`](patterns/esp32_image.hexpat) | Firmware image format for the ESP32 chip family |
|
||||||
| EVTX | `application/x-ms-evtx` | [`patterns/evtx.hexpat`](patterns/evtx.hexpat) | MS Windows Vista Event Log |
|
| EVTX | `application/x-ms-evtx` | [`patterns/evtx.hexpat`](patterns/evtx.hexpat) | MS Windows Vista Event Log |
|
||||||
| EXFAT | | [`patterns/fs/exfat.hexpat`](patterns/fs/exfat.hexpat) | Extensible File Allocation Table (exFAT) |
|
| EXFAT | | [`patterns/fs/exfat.hexpat`](patterns/fs/exfat.hexpat) | Extensible File Allocation Table (exFAT) |
|
||||||
|
| EXFAT (DFIR) | | [`patterns/DFIR/exFAT.hexpat`](patterns/DFIR/exFAT.hexpat) | Imported by DISK_PARSER.hexpat |
|
||||||
| EXT4 | | [`patterns/fs/ext4.hexpat`](patterns/fs/ext4.hexpat) | Ext4 File System |
|
| EXT4 | | [`patterns/fs/ext4.hexpat`](patterns/fs/ext4.hexpat) | Ext4 File System |
|
||||||
| 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 |
|
| 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 |
|
||||||
| FAT32 | | [`patterns/fs/fat32.hexpat`](patterns/fs/fat32.hexpat) | FAT32 File System |
|
| FAT32 | | [`patterns/fs/fat32.hexpat`](patterns/fs/fat32.hexpat) | FAT32 File System |
|
||||||
|
| FAT32 (DFIR) | | [`patterns/DFIR/FAT32.hexpat`](patterns/DFIR/FAT32.hexpat) | Imported by DISK_PARSER.hexpat |
|
||||||
| FBX | | [`patterns/fbx.hexpat`](patterns/fbx.hexpat) | Kaydara FBX Binary |
|
| FBX | | [`patterns/fbx.hexpat`](patterns/fbx.hexpat) | Kaydara FBX Binary |
|
||||||
| FDT | | [`patterns/fdt.hexpat`](patterns/fdt.hexpat) | Flat Linux Device Tree blob |
|
| FDT | | [`patterns/fdt.hexpat`](patterns/fdt.hexpat) | Flat Linux Device Tree blob |
|
||||||
| FFX | | [`patterns/ffx/*`](https://gitlab.com/EvelynTSMG/imhex-ffx-pats) | Various Final Fantasy X files |
|
| FFX | | [`patterns/ffx/*`](https://gitlab.com/EvelynTSMG/imhex-ffx-pats) | Various Final Fantasy X files |
|
||||||
@@ -134,6 +137,7 @@ Everything will immediately show up in ImHex's Content Store and gets bundled wi
|
|||||||
| NRO | | [`patterns/nro.hexpat`](patterns/nro.hexpat) | Nintendo Switch NRO files |
|
| NRO | | [`patterns/nro.hexpat`](patterns/nro.hexpat) | Nintendo Switch NRO files |
|
||||||
| NTAG | | [`patterns/ntag.hexpat`](patterns/ntag.hexpat) | NTAG213/NTAG215/NTAG216, NFC Forum Type 2 Tag compliant IC |
|
| NTAG | | [`patterns/ntag.hexpat`](patterns/ntag.hexpat) | NTAG213/NTAG215/NTAG216, NFC Forum Type 2 Tag compliant IC |
|
||||||
| NTFS | | [`patterns/fs/ntfs.hexpat`](patterns/fs/ntfs.hexpat) | NTFS (NT File System) |
|
| NTFS | | [`patterns/fs/ntfs.hexpat`](patterns/fs/ntfs.hexpat) | NTFS (NT File System) |
|
||||||
|
| NTFS (DFIR) | | [`patterns/DFIR/NTFS.hexpat`](patterns/DFIR/NTFS.hexpat) | Imported by DISK_PARSER.hexpat |
|
||||||
| OGG | `audio/ogg` | [`patterns/ogg.hexpat`](patterns/ogg.hexpat) | OGG Audio format |
|
| OGG | `audio/ogg` | [`patterns/ogg.hexpat`](patterns/ogg.hexpat) | OGG Audio format |
|
||||||
| ORP / ORS | | [`patterns/orp.hexpat`](patterns/orp.hexpat) | OpenRGB profile format |
|
| ORP / ORS | | [`patterns/orp.hexpat`](patterns/orp.hexpat) | OpenRGB profile format |
|
||||||
| PACK | | [`patterns/roblox_pack.hexpat`](patterns/roblox_pack.hexpat) | Roblox shader archive format |
|
| PACK | | [`patterns/roblox_pack.hexpat`](patterns/roblox_pack.hexpat) | Roblox shader archive format |
|
||||||
|
|||||||
81
patterns/DFIR/DFIR_README.md
Normal file
81
patterns/DFIR/DFIR_README.md
Normal 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)
|
||||||
|
|
||||||
|
|
||||||
677
patterns/DFIR/DISK_PARSER.hexpat
Normal file
677
patterns/DFIR/DISK_PARSER.hexpat
Normal 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 3–47: Reserved for UEFI")]];
|
||||||
|
reserved_MS : 12 [[comment("Bits 48–59: 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
789
patterns/DFIR/FAT32.hexpat
Normal 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
1571
patterns/DFIR/NTFS.hexpat
Normal file
File diff suppressed because it is too large
Load Diff
616
patterns/DFIR/exFAT.hexpat
Normal file
616
patterns/DFIR/exFAT.hexpat
Normal 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(" ");
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user