mirror of
https://github.com/WerWolv/ImHex-Patterns.git
synced 2026-03-28 07:47:02 -05:00
* 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>
790 lines
31 KiB
Rust
790 lines
31 KiB
Rust
#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(" ");
|
|
}
|