//------------------------------------------------ //--- 010 Editor v7.0 Binary Template // // File: Drive.bt // Authors: SweetScape Software, Benjamin Vernoux // Version: 3.2.1 // Purpose: Parse logical and physical drives including // MBR, FAT12, FAT16, FAT32, exFAT, HFS, NTFS and extended partitions. // Can display subdirectories and data for individual // files. Note that some NTFS drives // may not work properly (see file comments). // Category: Drives // File Mask: Drive*,Physical* // ID Bytes: [+510] 55 AA,[+1024] 48 2B,[+1024] 48 58 // History: // 3.2.1 2022-11-03 SweetScape Software: Fix for FAT_JumpToCluster and some 64-bit values. // 3.2 2021-11-10 SweetScape Software: Fixes for some NTFS and exFAT drives. Added $Max parsing. // 3.1 2021-09-17 AJacko: Added more detailed handling of NTFS reparse points // 3.0 2018-12-24 HenryGab: Add support for exFAT and 4k native sector sizes. // 2.6 2018-11-14 HenryGab: Add support for FAT12. // 2.4 2018-11-13 HenryGab: Fix file system recognition sequence // 2.2 2018-10-23 HenryGab: Fix multiple bugs in FAT16/FAT32. // 2.0 2016-07-06 SweetScape Software: Added support for HFS drives (macOS/iOS). // 1.1 2016-06-14 SweetScape Software: Added support for EFI partitions. // 1.0 2016-05-13 SweetScape Software: Added NTFS support, // parsing FAT files and subdirectories, // extended partitions. Created from original // MBRTemplateFAT.bt file with major rework // and merged information from other templates. // 0.1 2013-01-14 B Vernoux: Initial release. // // NTFS IMPORTANT NOTE: // Not all NTFS drives can be parsed correctly. Some NTFS // sectors use a special Fixup value where the last two // bytes of a sector are copied to a different location // in order to detect bad sectors. This template cannot // properly handle the Fixup values and if the Fixup // value hits critical information the template may stop. //------------------------------------------------ // TODO: // [ ] Mandatory parameters for ALL Drives: // local FILE_SYSTEM_TYPE FsType; // to indicate the file system type of that drive // local BYTE DriveBytesPerSectorShift; // to indicate sector size ... presumed 512 if does not exist // [ ] Add hacks to prevent accidental optimized-array instantiations... // LittleEndian(); typedef enum { boolean_false = 0, boolean_true = 1, boolean_true_negative_one = -1, boolean_just_use_true_and_false = 0xbaadf00d } boolean; local const ushort MAX_POTENTIAL_PARTITION_LOCATIONS = 0x40; local const boolean IFS_VARIABLE_LENGTH_STRUCTURE = false; local int NumDrives = 0; local const BYTE SupportedBytesPerSectorShift[2] = { 9, 12 }; boolean IsExactlyOneBitSet( UQUAD value ) { return 0 == (value & (value-1)); } BYTE BytesPerSectorToBytesPerSectorShift(DWORD BytesPerSector) { local DWORD i; local DWORD tmp; Assert(IsExactlyOneBitSet(BytesPerSector)); // 1 == 2^0 i = 0; tmp = BytesPerSector; while (tmp > 1) { tmp >>= 1; i++; } return i; } DWORD BytesPerSectorShiftToBytesPerSector(BYTE BytesPerSectorShift) { local DWORD result; Assert(IsValidBytesPerSectorShift(BytesPerSectorShift)); result = 1; result <<= BytesPerSectorShift; return result; } boolean IsValidBytesPerSectorShift(BYTE BytesPerSectorShift) { return (BytesPerSectorShift >= 9) && // minimum is 512 byte sectors (BytesPerSectorShift <= 12) ; // maximum is 4k sectors } //################################################################ // MBR - Master Boot Record (contains partition information) //################################################################ // Partition Types typedef enum { PARTITION_SYSTEMID_EMPTY = 0, PARTITION_SYSTEMID_FAT_12 = 1, PARTITION_SYSTEMID_XENIX_ROOT = 2, PARTITION_SYSTEMID_XENIX_USR = 3, PARTITION_SYSTEMID_FAT_16_INF32MB = 4, PARTITION_SYSTEMID_EXTENDED = 5, PARTITION_SYSTEMID_FAT_16 = 6, PARTITION_SYSTEMID_NTFS_HPFS_EXFAT = 7, PARTITION_SYSTEMID_AIX = 8, PARTITION_SYSTEMID_AIX_BOOT = 9, PARTITION_SYSTEMID_OS2_BOOT_MGR = 10, PARTITION_SYSTEMID_PRI_FAT32_INT13 = 11, PARTITION_SYSTEMID_EXT_FAT32_INT13 = 12, PARTITION_SYSTEMID_SILICON_SAFE = 13, PARTITION_SYSTEMID_EXT_FAT16_INT13 = 14, PARTITION_SYSTEMID_WIN95_EXT_PARTITION = 15, PARTITION_SYSTEMID_OPUS = 16, PARTITION_SYSTEMID_FAT_12_HIDDEN = 17, PARTITION_SYSTEMID_COMPAQ_DIAG = 18, PARTITION_SYSTEMID_FAT_16_HIDDEN_INF32MB = 20, PARTITION_SYSTEMID_FAT_16_HIDDEN = 22, PARTITION_SYSTEMID_NTFS_HPFS_HIDDEN = 23, PARTITION_SYSTEMID_AST_SMARTSLEEP_PARTITION = 24, PARTITION_SYSTEMID_OSR2_FAT32 = 27, PARTITION_SYSTEMID_OSR2_FAT32_LBA = 28, PARTITION_SYSTEMID_HIDDEN_FAT16_LBA = 30, PARTITION_SYSTEMID_NEC_DOS = 36, PARTITION_SYSTEMID_PQSERVICE_ROUTERBOOT = 39, PARTITION_SYSTEMID_ATHEOS_FILE_SYSTEM = 42, PARTITION_SYSTEMID_NOS = 50, PARTITION_SYSTEMID_JFS_ON_OS2_OR_ECS = 53, PARTITION_SYSTEMID_THEOS_2GB = 56, PARTITION_SYSTEMID_PLAN_9_THEOS_SPANNED = 57, PARTITION_SYSTEMID_THEOS_4GB = 58, PARTITION_SYSTEMID_THEOS_EXTENDED = 59, PARTITION_SYSTEMID_PARTITIONMAGIC_RECOVERY = 60, PARTITION_SYSTEMID_HIDDEN_NETWARE = 61, PARTITION_SYSTEMID_VENIX = 64, PARTITION_SYSTEMID_LINUX_PPC_PREP = 65, PARTITION_SYSTEMID_LINUX_SWAP = 66, PARTITION_SYSTEMID_LINUX_NATIVE = 67, PARTITION_SYSTEMID_GOBACK = 68, PARTITION_SYSTEMID_BOOT_US_EUMEL_ELAN = 69, PARTITION_SYSTEMID_EUMEL_ELAN_1 = 70, PARTITION_SYSTEMID_EUMEL_ELAN_2 = 71, PARTITION_SYSTEMID_EUMEL_ELAN_3 = 72, PARTITION_SYSTEMID_OBERON = 76, PARTITION_SYSTEMID_QNX4_X = 77, PARTITION_SYSTEMID_QNX4_X_2ND_PART = 78, PARTITION_SYSTEMID_QNX4_X_3RD_PART_OBERON = 79, PARTITION_SYSTEMID_ONTRACK_LYNX_OBERON = 80, PARTITION_SYSTEMID_ONTRACK_NOVELL = 81, PARTITION_SYSTEMID_CP_M_MICROPORT_SYSV_AT = 82, PARTITION_SYSTEMID_DISK_MANAGER_AUX3 = 83, PARTITION_SYSTEMID_DISK_MANAGER_DDO = 84, PARTITION_SYSTEMID_EZ_DRIVE = 85, PARTITION_SYSTEMID_GOLDEN_BOW_EZ_BIOS = 86, PARTITION_SYSTEMID_DRIVEPRO_VNDI = 87, PARTITION_SYSTEMID_PRIAM_EDISK = 92, PARTITION_SYSTEMID_SPEEDSTOR = 97, PARTITION_SYSTEMID_GNU_HURD = 99, PARTITION_SYSTEMID_NOVELL = 100, PARTITION_SYSTEMID_NETWARE_386 = 101, PARTITION_SYSTEMID_NETWARE_SMS_PARTITION = 102, PARTITION_SYSTEMID_NOVELL_1 = 103, PARTITION_SYSTEMID_NOVELL_2 = 104, PARTITION_SYSTEMID_NETWARE_NSS = 105, PARTITION_SYSTEMID_DISKSECURE_MULTI_BOOT = 112, PARTITION_SYSTEMID_V7_X86 = 114, PARTITION_SYSTEMID_PC_IX = 117, PARTITION_SYSTEMID_M2FS_M2CS_VNDI = 119, PARTITION_SYSTEMID_XOSL_FS = 120, PARTITION_SYSTEMID_MINUX_OLD = 128, PARTITION_SYSTEMID_MINUX_LINUX = 129, PARTITION_SYSTEMID_LINUX_SWAP_2 = 130, PARTITION_SYSTEMID_LINUX_NATIVE_2 = 131, PARTITION_SYSTEMID_OS2_HIDDEN_HIBERNATION = 132, PARTITION_SYSTEMID_LINUX_EXTENDED = 133, PARTITION_SYSTEMID_OLD_LINUX_RAID_FAT16 = 134, PARTITION_SYSTEMID_NTFS_VOLUME_SET = 135, PARTITION_SYSTEMID_LINUX_PLAINTEXT_TABLE = 136, PARTITION_SYSTEMID_LINUX_KERNEL_AIR_BOOT = 138, PARTITION_SYSTEMID_FAULT_TOLERANT_FAT32 = 139, PARTITION_SYSTEMID_FAULT_TOLERANT_FAT32_INT13H = 140, PARTITION_SYSTEMID_FREE_FDISK_FAT12 = 141, PARTITION_SYSTEMID_LINUX_LOGICAL_VOLUME_MANAGER = 142, PARTITION_SYSTEMID_FREE_FDISK_PRIMARY_FAT16 = 144, PARTITION_SYSTEMID_FREE_FDISK_EXTENDED = 145, PARTITION_SYSTEMID_FREE_FDISK_LARGE_FAT16 = 146, PARTITION_SYSTEMID_AMOEBA = 147, PARTITION_SYSTEMID_AMOEBA_BBT = 148, PARTITION_SYSTEMID_MIT_EXOPC = 149, PARTITION_SYSTEMID_CHRP_ISO_9660 = 150, PARTITION_SYSTEMID_FREE_FDISK_FAT32 = 151, PARTITION_SYSTEMID_FREE_FDISK_FAT32_LBA = 152, PARTITION_SYSTEMID_DCE376 = 153, PARTITION_SYSTEMID_FREE_FDISK_FAT16_LBA = 154, PARTITION_SYSTEMID_FREE_FDISK_EXTENDED_LBA = 155, PARTITION_SYSTEMID_FORTHOS = 158, PARTITION_SYSTEMID_BSD_OS = 159, PARTITION_SYSTEMID_LAPTOP_HIBERNATION = 160, PARTITION_SYSTEMID_LAPTOP_HIBERNATION_HP = 161, PARTITION_SYSTEMID_HP_EXPANSION_SPEEDSTOR_1 = 163, PARTITION_SYSTEMID_HP_EXPANSION_SPEEDSTOR_2 = 164, PARTITION_SYSTEMID_BSD_386 = 165, PARTITION_SYSTEMID_OPENBSD_SPEEDSTOR = 166, PARTITION_SYSTEMID_NEXTSTEP = 167, PARTITION_SYSTEMID_MAC_OS_X = 168, PARTITION_SYSTEMID_NETBSD = 169, PARTITION_SYSTEMID_OLIVETTI = 170, PARTITION_SYSTEMID_MAC_OS_X_BOOT_GO = 171, PARTITION_SYSTEMID_RISC_OS_ADFS = 173, PARTITION_SYSTEMID_SHAGOS = 174, PARTITION_SYSTEMID_SHAGOS_SWAP_MACOS_X_HFS = 175, PARTITION_SYSTEMID_BOOTSTAR_DUMMY = 176, PARTITION_SYSTEMID_HP_EXPANSION_QNX = 177, PARTITION_SYSTEMID_QNX_POWER_SAFE = 178, PARTITION_SYSTEMID_HP_EXPANSION_QNX_2 = 179, PARTITION_SYSTEMID_HP_EXPANSION_SPEEDSTOR_3 = 180, PARTITION_SYSTEMID_HP_EXPANSION_FAT16 = 182, PARTITION_SYSTEMID_BSDI_FS = 183, PARTITION_SYSTEMID_BSDI_SWAP = 184, PARTITION_SYSTEMID_BOOT_WIZARD_HIDDEN = 187, PARTITION_SYSTEMID_ACRONIS_BACKUP = 188, PARTITION_SYSTEMID_BONNYDOS_286 = 189, PARTITION_SYSTEMID_SOLARIS_8_BOOT = 190, PARTITION_SYSTEMID_NEW_SOLARIS = 191, PARTITION_SYSTEMID_CTOS_REAL_32_DR_DOS = 192, PARTITION_SYSTEMID_DRDOS_SECURED = 193, PARTITION_SYSTEMID_HIDDEN_LINUX_SWAP = 195, PARTITION_SYSTEMID_DRDOS_SECURED_FAT16 = 196, PARTITION_SYSTEMID_DRDOS_SECURED_EXTENDED = 197, PARTITION_SYSTEMID_DRDOS_SECURED_FAT16_STRIPE = 198, PARTITION_SYSTEMID_SYRINX = 199, PARTITION_SYSTEMID_DR_DOS_8_1 = 200, PARTITION_SYSTEMID_DR_DOS_8_2 = 201, PARTITION_SYSTEMID_DR_DOS_8_3 = 202, PARTITION_SYSTEMID_DR_DOS_7_SECURED_FAT32_CHS = 203, PARTITION_SYSTEMID_DR_DOS_7_SECURED_FAT32_LBA = 204, PARTITION_SYSTEMID_CTOS_MEMDUMP = 205, PARTITION_SYSTEMID_DR_DOS_7_FAT16X = 206, PARTITION_SYSTEMID_DR_DOS_7_SECURED_EXT_DOS = 207, PARTITION_SYSTEMID_REAL_32_SECURE = 208, PARTITION_SYSTEMID_OLD_MULTIUSER_FAT12 = 209, PARTITION_SYSTEMID_OLD_MULTIUSER_FAT16 = 212, PARTITION_SYSTEMID_OLD_MULTIUSER_EXTENDED = 213, PARTITION_SYSTEMID_OLD_MULTIUSER_FAT16_2 = 214, PARTITION_SYSTEMID_CP_M_86 = 216, PARTITION_SYSTEMID_NON_FS_DATA_POWERCOPY_BACKUP = 218, PARTITION_SYSTEMID_CP_M = 219, PARTITION_SYSTEMID_HIDDEN_CTOS_MEMDUMP = 221, PARTITION_SYSTEMID_DELL_POWEREDGE_UTIL = 222, PARTITION_SYSTEMID_DG_UX_DISK_MANAGER_BOOTIT = 223, PARTITION_SYSTEMID_ACCESS_DOS = 225, PARTITION_SYSTEMID_DOS_R_O = 227, PARTITION_SYSTEMID_SPEEDSTOR_FAT16_EXTENDED = 228, PARTITION_SYSTEMID_STORAGE_DIMENSIONS_SPEEDSTOR = 230, PARTITION_SYSTEMID_LUKS = 232, PARTITION_SYSTEMID_RUFUS_EXTRA_FREEDESKTOP = 234, PARTITION_SYSTEMID_BEOS_BFS = 235, PARTITION_SYSTEMID_SKYOS_SKYFS = 236, PARTITION_SYSTEMID_LEGACY_MBR_EFI_HEADER = 238, PARTITION_SYSTEMID_EFI_FS = 239, PARTITION_SYSTEMID_LINUX_PA_RISC_BOOT = 240, PARTITION_SYSTEMID_STORAGE_DIMENSIONS_SPEEDSTOR_2 = 241, PARTITION_SYSTEMID_DOS_SECONDARY = 242, PARTITION_SYSTEMID_SPEEDSTOR_LARGE_PROLOGUE = 244, PARTITION_SYSTEMID_PROLOGUE_MULTI_VOLUME = 245, PARTITION_SYSTEMID_STORAGE_DIMENSIONS_SPEEDSTOR_3 = 246, PARTITION_SYSTEMID_DDRDRIVE_SOLID_STATE_FS = 247, PARTITION_SYSTEMID_PCACHE = 249, PARTITION_SYSTEMID_BOCHS = 250, PARTITION_SYSTEMID_VMWARE_FILE_SYSTEM = 251, PARTITION_SYSTEMID_VMWARE_SWAP = 252, PARTITION_SYSTEMID_LINUX_RAID = 253, PARTITION_SYSTEMID_SPEEDSTOR_LANSTEP_LINUX = 254, PARTITION_SYSTEMID_BBT = 255, } PARTITION_SYSTEMID ; // hack to allow printing this when using an integer instead of byte.... string ReadPARTITION_SYSTEMID( int SystemIdAsInteger ) { local string result = ""; if (SystemIdAsInteger >= 0 && SystemIdAsInteger <= 0xFF) { local PARTITION_SYSTEMID tmp = (PARTITION_SYSTEMID)SystemIdAsInteger; result = EnumToString(tmp); } if (Strlen(result) == 0) { SPrintf(result, "Invalid (0x%x)", SystemIdAsInteger); } return result; } // Media descriptor typedef enum { FLOPPY = 0xf0, HARD_DRIVE = 0xf8, FLOPPY_320K_1 = 0xfa, FLOPPY_640K = 0xfb, FLOPPY_180K = 0xfc, FLOPPY_360K = 0xfd, FLOPPY_160K = 0xfe, FLOPPY_320K_2 = 0xff, } MEDIA ; // Boot Indicator Values typedef enum { NOBOOT = 0, SYSTEM_PARTITION = 0x80, } BOOTINDICATOR ; // GUID - Global Unique Identifier typedef struct { // See Eric Lippert's 2004 post: // https://blogs.msdn.microsoft.com/ericlippert/2004/05/25/you-cant-convert-data-structures-to-strings-in-vbscript-without-breaking-a-few-eggs/ // A GUID stored in binary format in memory is // a sixteen byte structure in the following format: // DWORD - WORD - WORD - BYTE BYTE - BYTE BYTE BYTE BYTE BYTE BYTE // it is printed in little-endian. local boolean was_big_endian = IsBigEndian(); LittleEndian(); UINT32 Part1; UINT16 Part2; UINT16 Part3; UBYTE Part4[2]; UBYTE Part5[6]; if (was_big_endian) { BigEndian(); } } GUID ; string ReadGUID( GUID &g ) { local boolean was_big_endian ; local string result ; was_big_endian = IsBigEndian(); LittleEndian(); SPrintf( result, "{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}", g.Part1, g.Part2, g.Part3, g.Part4[0], g.Part4[1], g.Part5[0], g.Part5[1], g.Part5[2], g.Part5[3], g.Part5[4], g.Part5[5] ); if (was_big_endian) { BigEndian(); } return result; } int WriteGUID( GUID &g, string s ) { local boolean was_big_endian ; local int read_arguments ; local int result ; was_big_endian = IsBigEndian(); LittleEndian(); result = -1; // per docs, return 0 on success, -1 on failure if (IsValidGuidString(s)) { // prevent partially-written results // copy from ReadGUID() function read_arguments = SScanf( s, "{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}", g.Part1, g.Part2, g.Part3, g.Part4[0], g.Part4[1], g.Part5[0], g.Part5[1], g.Part5[2], g.Part5[3], g.Part5[4], g.Part5[5] ); if (read_arguments == 11) { // only success when can read all eleven parts result = 0; } } if (was_big_endian) { BigEndian(); } return result; } boolean IsValidGuidString( string guid_string ) { // required input format: // "{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}" // regex special characters are: ".[]^$()/\*{}?+|" return RegExMatch(guid_string, "^\{\h{8}-\h{4}-\h{4}-\h{4}-\h{12}\}$"); } boolean IsGuidNonZero( const GUID &g ) { return (0 != g.Part1 ) || (0 != g.Part2 ) || (0 != g.Part3 ) || (0 != g.Part4[0]) || (0 != g.Part4[1]) || (0 != g.Part5[0]) || (0 != g.Part5[1]) || (0 != g.Part5[2]) || (0 != g.Part5[3]) || (0 != g.Part5[4]) || (0 != g.Part5[5]) ; } // Partition Entry typedef struct { BOOTINDICATOR BootIndicator; UBYTE StartingHead ; WORD StartingSectCylinder ; // Need Bit fields PARTITION_SYSTEMID SystemID; UBYTE EndingHead ; WORD EndingSectCylinder ; // Need Bit fields DWORD RelativeSector ; DWORD TotalSectors ; } PARTITION_ENTRY ; // Show SystemID beside each partition string ReadPARTITION_ENTRY( PARTITION_ENTRY &p ) { local string s ; s = EnumToString( p.SystemID ); if( Strlen(s) == 0 ) SPrintf( s, "(%d)", p.SystemID ); return s; } // MBR - Master Boot Record typedef struct { UBYTE BootCode[446] ; PARTITION_ENTRY partitions[4]; WORD EndOfSectorMarker ; } MASTER_BOOT_RECORD; // Extended partition typedef struct { UBYTE Empty[446] ; PARTITION_ENTRY partitions[4]; WORD EndOfSectorMarker ; } EXTENDED_PARTITION ; //################################################################ // EFI (Extensible Firmware Interface) Partitions //################################################################ // EFI Type - First 4 bytes of the GUID typedef enum { EFI_UNUSED = 0x00000000, // {00000000-0000-0000-0000-000000000000} EFI_MBR = 0x024DEE41, // {024DEE41-33E7-11D3-9D69-0008C781F39F} EFI_SYSTEM = 0xC12A7328, // {C12A7328-F81F-11D2-BA4B-00A0C93EC93B} EFI_BIOS_BOOT = 0x21686148, // {21686148-6449-6E6F-744E-656564454649} EFI_IFFS = 0xD3BFE2DE, // {D3BFE2DE-3DAF-11DF-BA40-E3A556D89593} EFI_SONY_BOOT = 0xF4019732, // {F4019732-066E-4E12-8273-346C5641494F} EFI_LENOVO_BOOT = 0xBFBFAFE7, // {BFBFAFE7-A34F-448A-9A5B-6213EB736C22} EFI_MSR = 0xE3C9E316, // {E3C9E316-0B5C-4DB8-817D-F92DF00215AE} EFI_BASIC_DATA = 0xEBD0A0A2, // {EBD0A0A2-B9E5-4433-87C0-68B6B72699C7} EFI_BASIC_DATA2 = 0xA2A0D0EB, // {A2A0D0EB-E5B9-3344-87C0-68B6B72699C7} // windows/linux data partition EFI_LDM_META = 0x5808C8AA, // {5808C8AA-7E8F-42E0-85D2-E1E90434CFB3} EFI_LDM = 0xAF9B60A0, // {AF9B60A0-1431-4F62-BC68-3311714A69AD} EFI_RECOVERY = 0xDE94BBA4, // {DE94BBA4-06D1-4D40-A16A-BFD50179D6AC} EFI_GPFS = 0x37AFFC90, // {37AFFC90-EF7D-4E96-91C3-2D7AE055B174} EFI_STORAGE_SPACES = 0xE75CAF8F, // {E75CAF8F-F680-4CEE-AFA3-B001E56EFC2D} EFI_HPUX_DATA = 0x75894C1E, // {75894C1E-3AEB-11D3-B7C1-7B03A0000000} EFI_HPUX_SERVICE = 0xE2A1E728, // {E2A1E728-32E3-11D6-A682-7B03A0000000} EFI_LINUX_DATA = 0x0FC63DAF, // {0FC63DAF-8483-4772-8E79-3D69D8477DE4} EFI_LINUX_RAID = 0xA19D880F, // {A19D880F-05FC-4D3B-A006-743F0F84911E} EFI_LINUX_ROOT32 = 0x44479540, // {44479540-F297-41B2-9AF7-D131D5F0458A} EFI_LINUX_ROOT64 = 0x4F68BCE3, // {4F68BCE3-E8CD-4DB1-96E7-FBCAF984B709} EFI_LINUX_ROOT_ARM32 = 0x69DAD710, // {69DAD710-2CE4-4E3C-B16C-21A1D49ABED3} EFI_LINUX_ROOT_ARM64 = 0xB921B045, // {B921B045-1DF0-41C3-AF44-4C6F280D3FAE} EFI_LINUX_ROOT_IA64 = 0x993d8d3d, // {993d8d3d-f80e-4225-855a-9daf8ed7ea97} EFI_LINUX_ROOT_VERITY32 = 0xd13c5d3b, // {d13c5d3b-b5d1-422a-b29f-9454fdc89d76} EFI_LINUX_ROOT_VERITY64 = 0x2c7357ed, // {2c7357ed-ebd2-46d9-aec1-23d437ec2bf5} EFI_LINUX_ROOT_VERITY_ARM32 = 0x7386cdf2, // {7386cdf2-203c-47a9-a498-f2ecce45a2d6} EFI_LINUX_ROOT_VERITY_ARM64 = 0xdf3300ce, // {df3300ce-d69f-4c92-978c-9bfb0f38d820} EFI_LINUX_ROOT_VERITY_IA64 = 0x86ed10d5, // {86ed10d5-b607-45bb-8957-d350f23d0571} EFI_LINUX_DATA2 = 0xAF3DC60F, // {AF3DC60F-8384-7247-8E79-3D69D8477DE4} EFI_LINUX_HOME = 0x933AC7E1, // {933AC7E1-2EB4-4F13-B844-0E14E2AEF915} EFI_LINUX_SRV = 0x3B8F8425, // {3B8F8425-20E0-4F3B-907F-1A25A76F98E8} EFI_LINUX_SWAP = 0x0657FD6D, // {0657FD6D-A4AB-43C4-84E5-0933C84B4F4F} EFI_LINUX_LVM = 0xE6D6D379, // {E6D6D379-F507-44C2-A23C-238F2A3DF928} EFI_LINUX_DM_CRYPT = 0x7FFEC5C9, // {7FFEC5C9-2D00-49B7-8941-3EA10A5586B7} EFI_LINUX_LUKS = 0xCA7D7CCB, // {CA7D7CCB-63ED-4C53-861C-1742536059CC} EFI_LINUX_RESERVED = 0x8DA63339, // {8DA63339-0007-60C0-C436-083AC8230908} EFI_FREEBSD_BOOT = 0x83BD6B9D, // {83BD6B9D-7F41-11DC-BE0B-001560B84F0F} EFI_FREEBSD_DATA = 0x516E7CB4, // {516E7CB4-6ECF-11D6-8FF8-00022D09712B} EFI_FREEBSD_SWAP = 0x516E7CB5, // {516E7CB5-6ECF-11D6-8FF8-00022D09712B} EFI_FREEBSD_UFS = 0x516E7CB6, // {516E7CB6-6ECF-11D6-8FF8-00022D09712B} EFI_FREEBSD_VINUM = 0x516E7CB8, // {516E7CB8-6ECF-11D6-8FF8-00022D09712B} EFI_FREEBSD_ZFS = 0x516E7CBA, // {516E7CBA-6ECF-11D6-8FF8-00022D09712B} EFI_OSX_HFS = 0x48465300, // {48465300-0000-11AA-AA11-00306543ECAC} EFI_OSX_APFS = 0x7C3457EF, // {7C3457EF-0000-11AA-AA11-00306543ECAC} EFI_OSX_UFS = 0x55465300, // {55465300-0000-11AA-AA11-00306543ECAC} EFI_OSX_ZFS = 0x6A898CC3, // {6A898CC3-1DD2-11B2-99A6-080020736631} EFI_OSX_RAID = 0x52414944, // {52414944-0000-11AA-AA11-00306543ECAC} EFI_OSX_RAID_OFFLINE = 0x52414944, // {52414944-5F4F-11AA-AA11-00306543ECAC} EFI_OSX_RECOVERY = 0x426F6F74, // {426F6F74-0000-11AA-AA11-00306543ECAC} EFI_OSX_LABEL = 0x4C616265, // {4C616265-6C00-11AA-AA11-00306543ECAC} EFI_OSX_TV_RECOVERY = 0x5265636F, // {5265636F-7665-11AA-AA11-00306543ECAC} EFI_OSX_CORE_STORAGE = 0x53746F72, // {53746F72-6167-11AA-AA11-00306543ECAC} EFI_OSX_SOFTRAID_STATUS = 0xB6FA30DA, // {B6FA30DA-92D2-4A9A-96F1-871EC6486200} EFI_OSX_SOFTRAID_SCRATCH = 0x2E313465, // {2E313465-19B9-463F-8126-8A7993773801} EFI_OSX_SOFTRAID_VOLUME = 0xFA709C7E, // {FA709C7E-65B1-4593-BFD5-E71D61DE9B02} EFI_OSX_SOFTRAID_CACHE = 0xBBBA6DF5, // {BBBA6DF5-F46F-4A89-8F59-8765B2727503} EFI_SOLARIS_BOOT = 0x6A82CB45, // {6A82CB45-1DD2-11B2-99A6-080020736631} EFI_SOLARIS_ROOT = 0x6A85CF4D, // {6A85CF4D-1DD2-11B2-99A6-080020736631} EFI_SOLARIS_SWAP = 0x6A87C46F, // {6A87C46F-1DD2-11B2-99A6-080020736631} EFI_SOLARIS_BACKUP = 0x6A8B642B, // {6A8B642B-1DD2-11B2-99A6-080020736631} EFI_SOLARIS_USR = 0x6A898CC3, // {6A898CC3-1DD2-11B2-99A6-080020736631} EFI_SOLARIS_VAR = 0x6A8EF2E9, // {6A8EF2E9-1DD2-11B2-99A6-080020736631} EFI_SOLARIS_HOME = 0x6A90BA39, // {6A90BA39-1DD2-11B2-99A6-080020736631} EFI_SOLARIS_ALTERNATE = 0x6A9283A5, // {6A9283A5-1DD2-11B2-99A6-080020736631} EFI_SOLARIS_RESERVED1 = 0x6A945A3B, // {6A945A3B-1DD2-11B2-99A6-080020736631} EFI_SOLARIS_RESERVED2 = 0x6A9630D1, // {6A9630D1-1DD2-11B2-99A6-080020736631} EFI_SOLARIS_RESERVED3 = 0x6A980767, // {6A980767-1DD2-11B2-99A6-080020736631} EFI_SOLARIS_RESERVED4 = 0x6A96237F, // {6A96237F-1DD2-11B2-99A6-080020736631} EFI_SOLARIS_RESERVED5 = 0x6A8D2AC7, // {6A8D2AC7-1DD2-11B2-99A6-080020736631} EFI_NETBSD_SWAP = 0x49F48D32, // {49F48D32-B10E-11DC-B99B-0019D1879648} EFI_NETBSD_FFS = 0x49F48D5A, // {49F48D5A-B10E-11DC-B99B-0019D1879648} EFI_NETBSD_LFS = 0x49F48D82, // {49F48D82-B10E-11DC-B99B-0019D1879648} EFI_NETBSD_RAID = 0x49F48DAA, // {49F48DAA-B10E-11DC-B99B-0019D1879648} EFI_NETBSD_CONCAT = 0x2DB519C4, // {2DB519C4-B10F-11DC-B99B-0019D1879648} EFI_NETBSD_ENCRYPT = 0x2DB519EC, // {2DB519EC-B10F-11DC-B99B-0019D1879648} EFI_CHROMEOS_KERNEL = 0xFE3A2A5D, // {FE3A2A5D-4F32-41A7-B725-ACCC3285A309} EFI_CHROMEOS_ROOTFS = 0x3CB8E202, // {3CB8E202-3B7E-47DD-8A3C-7FF2A13CFCEC} EFI_CHROMEOS_FUTURE = 0x2E0A753D, // {2E0A753D-9E48-43B0-8337-B15192CB1B5E} EFI_COREOS_USR = 0x5DFBF5F4, // {5DFBF5F4-2848-4BAC-AA5E-0D9A20B745A6} EFI_COREOS_ROOT = 0x3884DD41, // {3884DD41-8582-4404-B9A8-E9B84F2DF50E} EFI_COREOS_OEM = 0xC95DC21A, // {C95DC21A-DF0E-4340-8D7B-26CBFA9A03E0} EFI_COREOS_ROOT_RAID = 0xBE9067B9, // {BE9067B9-EA49-4F15-B4F6-F36F8C9E1818} EFI_HAIKU = 0x42465331, // {42465331-3BA3-10F1-802A-4861696B7521} EFI_MIDNIGHTBSD_BOOT = 0x85D5E45E, // {85D5E45E-237C-11E1-B4B3-E89A8F7FC3A7} EFI_MIDNIGHTBSD_DATA = 0x85D5E45A, // {85D5E45A-237C-11E1-B4B3-E89A8F7FC3A7} EFI_MIDNIGHTBSD_SWAP = 0x85D5E45B, // {85D5E45B-237C-11E1-B4B3-E89A8F7FC3A7} EFI_MIDNIGHTBSD_UFS = 0x0394EF8B, // {0394EF8B-237E-11E1-B4B3-E89A8F7FC3A7} EFI_MIDNIGHTBSD_VINUM = 0x85D5E45C, // {85D5E45C-237C-11E1-B4B3-E89A8F7FC3A7} EFI_MIDNIGHTBSD_ZFS = 0x85D5E45D, // {85D5E45D-237C-11E1-B4B3-E89A8F7FC3A7} EFI_CEPH_JOURNAL = 0x45B0969E, // {45B0969E-9B03-4F30-B4C6-B4B80CEFF106} EFI_CEPH_ENCRYPT = 0x45B0969E, // {45B0969E-9B03-4F30-B4C6-5EC00CEFF106} EFI_CEPH_OSD = 0x4FBD7E29, // {4FBD7E29-9D25-41B8-AFD0-062C0CEFF05D} EFI_CEPH_ENCRYPT_OSD = 0x4FBD7E29, // {4FBD7E29-9D25-41B8-AFD0-5EC00CEFF05D} EFI_CEPH_CREATE = 0x89C57F98, // {89C57F98-2FE5-4DC0-89C1-F3AD0CEFF2BE} EFI_CEPH_ENCRYPT_CREATE = 0x89C57F98, // {89C57F98-2FE5-4DC0-89C1-5EC00CEFF2BE} EFI_CEPH_BLOCK = 0xCAFECAFE, // {CAFECAFE-9B03-4F30-B4C6-B4B80CEFF106} EFI_CEPH_BLOCK_DB = 0x30CD0809, // {30CD0809-C2B2-499C-8879-2D6B78529876} EFI_CEPH_BLOCK_WRITE_AHEAD = 0x5CE17FCE, // {5CE17FCE-4087-4169-B7FF-056CC58473F9} EFI_CEPH_ENCRYPT_LOCKBOX = 0xFB3AABF9, // {FB3AABF9-D25F-47CC-BF5E-721D1816496B} EFI_CEPH_MULTIPATH_OSD = 0x4FBD7E29, // {4FBD7E29-8AE0-4982-BF9D-5A8D867AF560} EFI_CEPH_MULTIPATH_JOURNAL = 0x45B0969E, // {45B0969E-8AE0-4982-BF9D-5A8D867AF560} EFI_CEPH_MULTIPATH_BLOCK1 = 0xCAFECAFE, // {CAFECAFE-8AE0-4982-BF9D-5A8D867AF560} EFI_CEPH_MULTIPATH_BLOCK2 = 0x7F4A666A, // {7F4A666A-16F3-47A2-8445-152EF4D03F6C} EFI_CEPH_MULTIPATH_BLOCK_DB = 0xEC6D6385, // {EC6D6385-E346-45DC-BE91-DA2A7C8B3261} EFI_CEPH_MULTIPATH_BLOCK_WRITE_AHEAD = 0x01B41E1B, // {01B41E1B-002A-453C-9F17-88793989FF8F} EFI_CEPH_ENCRYPT_BLOCK = 0xCAFECAFE, // {CAFECAFE-9B03-4F30-B4C6-5EC00CEFF106} EFI_CEPH_ENCRYPT_BLOCK_DB = 0x93B0052D, // {93B0052D-02D9-4D8A-A43B-33A3EE4DFBC3} EFI_CEPH_ENCRYPT_BLOCK_WRITE_AHEAD = 0x306E8683, // {306E8683-4FE2-4330-B7C0-00A917C16966} EFI_CEPH_ENCRYPT_LUKS_JOURNAL = 0x45B0969E, // {45B0969E-9B03-4F30-B4C6-35865CEFF106} EFI_CEPH_ENCRYPT_LUKS_BLOCK = 0xCAFECAFE, // {CAFECAFE-9B03-4F30-B4C6-35865CEFF106} EFI_CEPH_ENCRYPT_LUKS_BLOCK_DB = 0x166418DA, // {166418DA-C469-4022-ADF4-B30AFD37F176} EFI_CEPH_ENCRYPT_LUKS_BLOCK_WRITE_AHEAD = 0x86A32090, // {86A32090-3647-40B9-BBBD-38D8C573AA86} EFI_CEPH_ENCRYPT_LUKS_OSD = 0x4FBD7E29, // {4FBD7E29-9D25-41B8-AFD0-35865CEFF05D} EFI_OPENBSD = 0x824CC7A0, // {824CC7A0-36A8-11E3-890A-952519AD3F61} EFI_QNX = 0xCEF5A9AD, // {CEF5A9AD-73BC-4601-89F3-CDEEEEE321A1} EFI_PLAN9 = 0xC91818F9, // {C91818F9-8025-47AF-89D2-F030D7000C2C} EFI_VMWARE_VMKCORE = 0x9D275380, // {9D275380-40AD-11DB-BF97-000C2911D1B8} EFI_VMWARE_VMFS = 0xAA31E02A, // {AA31E02A-400F-11DB-9590-000C2911D1B8} EFI_VMWARE_RESERVED = 0x9198EFFC, // {9198EFFC-31C0-11DB-8F78-000C2911D1B8} } EFI_TYPE ; // EFI partition entry typedef struct (int size) { EFI_TYPE Type; // Use the first UInt32 of the GUID to show the type FSkip( -4 ); GUID TypeGUID; GUID PartitionGUID; UQUAD FirstLBA ; UQUAD LastLBA ; UQUAD System : 1; // Flags UQUAD Ignore : 1; UQUAD Legacy : 1; UQUAD : 58; UQUAD ReadOnly : 1; UQUAD Hidden : 1; UQUAD NoMount : 1; wchar_t Name[36]; // Some partitions may have extra space at the end if( size > 128 ) { UBYTE Unused[ size - 128 ] ; } } EFI_PARTITION_ENTRY ; // BUGBUG -- multiple parser issues as of v9.0.1: // EFI_PARTITION_ENTRY should be OK to optimize, even though parameterized, // because the existence and offsets of fields depends ONLY on parameter, // not instance data. The parser is not currently smart enough to know this? // However, the compiler throws a warning that it's unsafe. // EFI_PARTITION_ENTRY should also be OK to specify . // However, this fails due to a parser bug, where it "forgets" the parameter. // Show EFI partition name and EFI type beside the entry wstring ReadEFI_PARTITION_ENTRY( EFI_PARTITION_ENTRY &entry ) { return entry.Name + " (" + EnumToString( entry.Type ) + ")"; } // EFI partition typedef struct { local int count ; // EFI partition header struct EFI_PARTITION_HEADER { BYTE Signature[8] ; DWORD Revision ; DWORD HeaderSize ; DWORD HeaderCRC32 ; DWORD Reserved ; UQUAD CurrentLBA ; UQUAD BackupLBA ; UQUAD FirstUsableLBA ; UQUAD LastUsableLBA ; GUID DiskGUID; UQUAD PartitionLBA ; DWORD NumPartitions ; DWORD PartitionSize ; DWORD PartitionCRC32 ; UBYTE Empty[420] ; } header; // Check signature if( header.Signature == "EFI PART" ) { // Count valid partitions count = 0; while( (ReadUQuad(FTell()+count*header.PartitionSize) != 0) && (count < header.NumPartitions) ) { count++; } // Create array of partitions if( count > 0 ) { EFI_PARTITION_ENTRY partitions(header.PartitionSize)[count]; } } } EFI_PARTITION ; //################################################################ // File system support section //################################################################ typedef enum { FILE_SYSTEM_TYPE_NONE = 0x0000, FILE_SYSTEM_TYPE_IFS = 0x0010, FILE_SYSTEM_TYPE_NTFS = 0x0020, FILE_SYSTEM_TYPE_HPFS = 0x0040, FILE_SYSTEM_TYPE_FAT12 = 0x0100, FILE_SYSTEM_TYPE_FAT16 = 0x0200, FILE_SYSTEM_TYPE_FAT32 = 0x0400, FILE_SYSTEM_TYPE_EXFAT = 0x0800, } FILE_SYSTEM_TYPE ; local const uint32 FILE_SYSTEM_TYPE_INVALID_MASK = 0xFFFFF08F; local const uint32 FILE_SYSTEM_TYPE_VALID_MASK = 0x00000F70; local const uint32 FILE_SYSTEM_TYPE_ANY_FAT_MASK = 0x00000700; local const uint32 MAX_UINT32 = 0xFFFFFFFF; Assert((MAX_UINT32 ^ (FILE_SYSTEM_TYPE_INVALID_MASK ^ FILE_SYSTEM_TYPE_VALID_MASK)) == 0); Assert((MAX_UINT32 ^ (FILE_SYSTEM_TYPE_INVALID_MASK | FILE_SYSTEM_TYPE_VALID_MASK)) == 0); string ReadFILE_SYSTEM_TYPE(const FILE_SYSTEM_TYPE &type) { if (type == FILE_SYSTEM_TYPE_NONE) { return "FILE_SYSTEM_TYPE_NONE"; } local string result ; local uint32 invalidFlags ; local string tmp ; result = ""; if (type & FILE_SYSTEM_TYPE_EXFAT ) { result += "| EXFAT"; } if (type & FILE_SYSTEM_TYPE_FAT32 ) { result += "| FAT32"; } if (type & FILE_SYSTEM_TYPE_FAT16 ) { result += "| FAT16"; } if (type & FILE_SYSTEM_TYPE_FAT12 ) { result += "| FAT12"; } if (type & FILE_SYSTEM_TYPE_HPFS ) { result += "| HPFS"; } if (type & FILE_SYSTEM_TYPE_NTFS ) { result += "| NTFS"; } if (type & FILE_SYSTEM_TYPE_IFS ) { result += "| IFS"; } invalidFlags = ((uint32)type) & FILE_SYSTEM_TYPE_INVALID_MASK; if (0 != invalidFlags) { SPrintf(tmp, "| 0x%x", type & invalidFlags); result += tmp; } result = SubStr(result, 2); return result; } //################################################################ // IFS Drives //################################################################ typedef struct (BYTE BytesPerSectorShift) { // see FILE_SYSTEM_RECOGNITION_STRUCTURE from Microsoft docs local FILE_SYSTEM_TYPE FsType = FILE_SYSTEM_TYPE_IFS; // to indicate the file system type of that drive local DWORD bytesPerSector = BytesPerSectorShiftToBytesPerSector(BytesPerSectorShift); local UINT32 ExpectedChecksum; // BUGBUG: Remove to avoid issues with optimized arrays BYTE jmp[3] ; CHAR OemName[8]; BYTE MustBeZero[5] ; DWORD Identifier ; // must be 0x53525346 for Windows IFS USHORT Length ; // must be 0x18 (24?) ?? or allowed to be up to 64k? USHORT Checksum; ExpectedChecksum = Calculate_IFS_BOOTSECTOR_Checksum(this); // Only add additional data when IFS structure is variable length AND // checksum matches expected checksum AND there's actually additional data if (IFS_VARIABLE_LENGTH_STRUCTURE && (Length > 24) && (ExpectedChecksum == Checksum)) { BYTE AdditionalData[Length-24] ; if (bytesPerSector > (Length + 24)) { BYTE RemainingSectorData[ bytesPerSector - (Length-24) ]; } } else if ((Length-24) < bytesPerSector) { BYTE RemainingSectorData[ bytesPerSector - (Length-24) ]; } } IFS_BOOTSECTOR ; // BUGBUG - should have size attribute, but fails with structure having any parameters // use a larger type to allow non-valid values (any value over 0xFFFF) to indicate errors UINT32 Calculate_IFS_BOOTSECTOR_Checksum( IFS_BOOTSECTOR& boot ) { local USHORT result ; local USHORT i ; local int64 start ; local int64 end ; // If the IFS structure is not variable-lenght, then length MUST be 24 for a valid checksum if (!IFS_VARIABLE_LENGTH_STRUCTURE) { if (boot.Length != 24) { return 0xBAAD0001; } } if (boot.Length < 24) { return 0xBAAD0001; } // If the IFS structure goes beyond the end of file, then NO possible valid checksum start = startof(boot); end = start + boot.Length; if (end > FileSize()) { return 0xBAAD0002; } // Else calculate actual checksum result = 0; i = 0; for (i = 3; i < boot.Length; i++) { if (i == 22 || i == 23) { continue; } // skip the checksum itself result = ((result & 1) ? 0x8000 : 0) + ((result >> 1) & 0x7FFF) + ReadByte(start+i); } return result; } boolean IsValidIfsBootsector( IFS_BOOTSECTOR &boot) { if (boot.MustBeZero[0] != 0) return false; if (boot.MustBeZero[1] != 0) return false; if (boot.MustBeZero[2] != 0) return false; if (boot.MustBeZero[3] != 0) return false; if (boot.MustBeZero[4] != 0) return false; if (boot.Length != 24) return false; if (boot.Identifier != 0x53525346) return false; if (boot.ExpectedChecksum != boot.Checksum) return false; return true; } typedef struct (BYTE BytesPerSectorShift) { local int DriveNum; local FILE_SYSTEM_TYPE FsType; local BYTE DriveBytesPerSectorShift = BytesPerSectorShift; DriveNum = NumDrives++; // keep track of the index of this drive FsType = FILE_SYSTEM_TYPE_IFS; IFS_BOOTSECTOR boot(BytesPerSectorShift); } IFS_DRIVE ; //################################################################ //################################################################ //################################################################ //################################################################ //################################################################ // // exFAT Drives // -- Much information found in US patent 8321439 // -- Also some information from NTFS.com // //################################################################ //################################################################ //################################################################ //################################################################ //################################################################ //================================================================ // exFAT struct forward declarations //================================================================ // Structures for the first 24 sectors on exFAT media (aka the two boot regions) struct EXFAT_BOOT_SECTOR; // (UBYTE BytesPerSectorShift); // boot region sector [0] struct EXFAT_BOOT_EXTENDED_SECTOR; // (UBYTE BytesPerSectorShift); // boot region sector [1..8] struct EXFAT_BOOT_OEM_PARAMETER_SECTOR;// (UBYTE BytesPerSectorShift); // boot region sector [9] struct EXFAT_BOOT_RESERVED_SECTOR; // (UBYTE BytesPerSectorShift); // boot region sector [10] struct EXFAT_BOOT_CHECKSUM_SECTOR; // (UBYTE BytesPerSectorShift); // boot region sector [11] struct EXFAT_BOOT_REGION; // (UBYTE BytesPerSectorShift); // the above twelve boot sectors make one boot region struct EXFAT_OEM_PARAMETER_TEMPLATE; // for the EXFAT_BOOT_OEM_PARAMETER_SECTOR sector... struct EXFAT_OEM_FLASH_PARAMETER; // for the EXFAT_BOOT_OEM_PARAMETER_SECTOR sector... // The FAT table follows the boot region struct EXFAT_FAT_TABLE; // (quad CountOfDataClusters); // An extent is simply a wrapper around a cluster index with an associated count of clusters struct EXFAT_EXTENT; // (quad ClusterCount); // This structure can be instantiated in-place when the next four bytes in the file are read as the startingClusterIndex // When instantiated, it has the full list of the cluster indices for a given file / directory // When NoFactChain flag is set AND at least one cluster is allocated, callers should set contiguousClusters to non-zero // Callers should not instantiate this unless at least one cluster is allocated. struct EXFAT_CHAINED_EXTENTS; // (EXFAT_FAT_TABLE &fat_table, EXFAT_CLUSTER_INFO startingClusterIndex, QUAD contiguousClusters); struct EXFAT_FILE; // (int DriveNum, const quad positions[], EXFAT_DIRENTRY_TYPE types[], uint16 secondaryEntryCount); // QWERTY - rename EXFAT_DIRECTORY to EXFAT_DIRECTORY_CONTENTS struct EXFAT_DIRECTORY_CONTENTS; // (int DriveNumber, EXFAT_CLUSTER_INFO startingClusterIndex, QUAD contiguousClusters); struct EXFAT_DATA; // (int DriveNumber, EXFAT_CLUSTER_INFO startingClusterIndex, boolean isContiguousClusters, UQUAD ValidDataLength); struct EXFAT_DIRENTRY_GENERIC; // last fallback struct EXFAT_DIRENTRY_GENERIC_PRIMARY; // primary entries struct EXFAT_DIRENTRY_GENERIC_SECONDARY; // secondary entries struct EXFAT_DIRENTRY_ALLOCATION_BITMAP; // CritPri struct EXFAT_DIRENTRY_UPCASE_TABLE; // CritPri struct EXFAT_DIRENTRY_VOLUME_LABEL; // CritPri struct EXFAT_DIRENTRY_FILE; // CritPri struct EXFAT_DIRENTRY_FILE_STREAM; // CritSec struct EXFAT_DIRENTRY_FILE_NAME; // CritSec struct EXFAT_DIRENTRY_VOLUME_GUID; // BenignPri struct EXFAT_DIRENTRY_TEXFAT_PADDING; // BenignPri // A directory entry set is a collection of directory entries. // It also includes corresponding file data. struct EXFAT_DIRENTRY_SET; // (int DriveNum, const quad positions[], uint16 validPositionCount) // Finally, the structure that pieces all the above together... struct EXFAT_DRIVE; //================================================================ // exFAT enums //================================================================ // Would prefer to have forward-definitions only at first.... // enum EXFAT_MEDIA_TYPE; // enum EXFAT_CLUSTER_INFO; // enum EXFAT_DIRENTRY_TYPE; typedef enum { EXFAT_MEDIA_HARD_DISK = 0xfffffff8, EXFAT_MEDIA_FLOPPY_DISK = 0xfffffff0 } EXFAT_MEDIA_TYPE ; typedef enum { EXFAT_CLUSTER_INVALID0 = 0x00000000, EXFAT_CLUSTER_INVALID1 = 0x00000001, EXFAT_CLUSTER_BAD_CLUSTER = 0xFFFFFFF7, EXFAT_CLUSTER_END_OF_CHAIN = 0xFFFFFFFF } EXFAT_CLUSTER_INFO ; typedef enum { EXFAT_DIRENTRY_TYPE_END = 0x00, // indicates end-of-directory, all later entries are to be ignored EXFAT_DIRENTRY_TYPE_INVALID = 0x80, // apparently invalid // // InUse (U) == 1xxx xxxx // Secondary (S) == x1xx xxxx (aka Category) // Benign (B) == xx1x xxxx (aka Importance) // Type Code (tc) == xxx1 1111 // EXFAT_DIRENTRY_TYPE_ALLOCATION_BITMAP = 0x81, // U=1, S=0, B=0, tc=01 (Crit Pri) EXFAT_DIRENTRY_TYPE_UPCASE_TABLE = 0x82, // U=1, S=0, B=0, tc=02 (Crit Pri) EXFAT_DIRENTRY_TYPE_VOLUME_LABEL = 0x83, // U=1, S=0, B=0, tc=03 (Crit Pri) EXFAT_DIRENTRY_TYPE_FILE = 0x85, // U=1, S=0, B=0, tc=05 (Crit Pri) EXFAT_DIRENTRY_TYPE_VOLUME_GUID = 0xA0, // U=1, S=0, B=1, tc=00 (Benign Pri) EXFAT_DIRENTRY_TYPE_TEXFAT_PADDING = 0xA1, // U=1, S=0, B=1, tc=01 (Benign Pri) EXFAT_DIRENTRY_TYPE_WINDOWSCE_ACL = 0xA2, // U=1, S=0, B=1, tc=02 (Benign Pri) EXFAT_DIRENTRY_TYPE_FILE_STREAM = 0xC0, // U=1, S=1, B=0, tc=00 (Crit Sec) EXFAT_DIRENTRY_TYPE_FILE_NAME = 0xC1, // U=1, S=1, B=0, tc=01 (Crit Sec) // no benign secondaries known yet... } EXFAT_DIRENTRY_TYPE ; //================================================================ // exFAT function forward declarations //================================================================ string ReadEXFAT_CLUSTER_INFO (const EXFAT_CLUSTER_INFO &info ); string ReadEXFAT_OEM_PARAMETER_TEMPLATE (const EXFAT_OEM_PARAMETER_TEMPLATE &p ); string ReadEXFAT_BOOT_CHECKSUM_SECTOR (const EXFAT_BOOT_CHECKSUM_SECTOR §or ); string ReadEXFAT_BOOT_REGION (const EXFAT_BOOT_REGION &boot_region ); string ReadEXFAT_DIRENTRY_TYPE (const EXFAT_DIRENTRY_TYPE type ); DWORD GenerateChainedExtent (const EXFAT_FAT_TABLE &fat_table, EXFAT_CLUSTER_INFO startingClusterIndex, QUAD contiguousClusters ); DWORD CalculateExpectedExfatBootRegionChecksumUsingStartPosition (quad start, BYTE BytesPerSectorShift); DWORD CalculateExpectedExfatBootRegionChecksum (const EXFAT_BOOT_REGION &boot_region); boolean IsValidExfatBootExtendedSector (const EXFAT_BOOT_EXTENDED_SECTOR &s); boolean IsValidExfatBootSector (const EXFAT_BOOT_SECTOR &boot); boolean IsValidExfatBootRegion (const EXFAT_BOOT_REGION &boot_region); boolean IsValidClusterIndex (const EXFAT_FAT_TABLE &fat_table, const EXFAT_CLUSTER_INFO clusterIndex); boolean IsDirectoryEntryUnused (const EXFAT_DIRENTRY_TYPE e) { return 0 == (e & 0x80); } boolean IsDirectoryEntryInUse (const EXFAT_DIRENTRY_TYPE e) { return 0 != (e & 0x80); } boolean IsDirectoryEntryPrimary (const EXFAT_DIRENTRY_TYPE e) { return 0 == (e & 0x40); } boolean IsDirectoryEntrySecondary(const EXFAT_DIRENTRY_TYPE e) { return 0 != (e & 0x40); } boolean IsDirectoryEntryCritical (const EXFAT_DIRENTRY_TYPE e) { return 0 == (e & 0x20); } boolean IsDirectoryEntryBenign (const EXFAT_DIRENTRY_TYPE e) { return 0 != (e & 0x20); } boolean IsDirectoryEntryCriticalPrimary (const EXFAT_DIRENTRY_TYPE e) { return IsDirectoryEntryCritical(e) && IsDirectoryEntryPrimary(e) ; } boolean IsDirectoryEntryCriticalSecondary(const EXFAT_DIRENTRY_TYPE e) { return IsDirectoryEntryCritical(e) && IsDirectoryEntrySecondary(e); } boolean IsDirectoryEntryBenignPrimary (const EXFAT_DIRENTRY_TYPE e) { return IsDirectoryEntryBenign(e) && IsDirectoryEntryPrimary(e) ; } boolean IsDirectoryEntryBenignSecondary (const EXFAT_DIRENTRY_TYPE e) { return IsDirectoryEntryBenign(e) && IsDirectoryEntrySecondary(e); } //================================================================ // exFAT constants //================================================================ local const char EXFAT_INVALID_FILENAME_CHARACTERS[0x29] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // Control codes 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, // Control codes 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, // Control codes 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, // Control codes 0x22, // Quotation mark 0x2A, // Asterisk 0x2F, // Forward Slash 0x3A, // Colon 0x3C, // less-than symbol, greater-than symbol, question mark, backslash 0x3E, // Greater-than symbol 0x3F, // Question mark 0x5C, // Backslash 0x7C // Vertical bar }; //================================================================ // exFAT "simple" structure definitions (contigous on media) //================================================================ //================================================================ // exFAT "complex" structure definitions //================================================================ // e.g., chained extents, directory entry sets, file data, ... //================================================================ // exFAT function implementations //================================================================ string ReadEXFAT_CLUSTER_INFO( const EXFAT_CLUSTER_INFO &info ) { local string s = ""; if (info == EXFAT_CLUSTER_INVALID0) { s += "INVALID (00000000h)"; } else if (info == EXFAT_CLUSTER_INVALID1) { s += "INVALID (00000001h)"; } else if (info == EXFAT_CLUSTER_BAD_CLUSTER) { s += "BAD CLUSTER (FFFFFFF7h)"; } else if (info == EXFAT_CLUSTER_END_OF_CHAIN) { s += "END_OF_CHAIN (FFFFFFFFh)"; } else { SPrintf(s, "%d", info); } // print in decimal to make easier to manually follow FAT chains... return s; } string ReadEXFAT_OEM_PARAMETER_TEMPLATE(const EXFAT_OEM_PARAMETER_TEMPLATE &p) { return ReadGUID(p.ParameterGuid); } string ReadEXFAT_BOOT_CHECKSUM_SECTOR( const EXFAT_BOOT_CHECKSUM_SECTOR §or ) { string s = SPrintf(s, "First Checksum (%08Xh)", sector.Checksum[0]); return s; } string ReadEXFAT_BOOT_REGION( const EXFAT_BOOT_REGION &boot_region) { local int i ; local string s ; local string result = ""; if (!boot_region.Boot.InternallyValid) { result += "Invalid (Boot sector)\r\n"; } i = 0; while (exists(boot_region.ExtendedBoot[i])) { if (!IsValidExfatBootExtendedSector(boot_region.ExtendedBoot[i])) { SPrintf(s, "Invalid (extended boot sector %d)\r\n", i); result += s; } i++; } if (!boot_region.ChecksumSector.InternallyValid) { result += "Invalid (ChecksumSector)\r\n"; } if (!boot_region.InternallyValid) { SPrintf(s, "Invalid (Checksum is not %08Xh)\r\n", boot_region.ChecksumSector.Checksum[0]); result += s; } if (0 == Strlen(result)) { result = SPrintf(s, "EXFAT (s/n %08Xh) @ %08Xh", boot_region.Boot.VolumeSerialNumber, startof(boot_region)); } return result; } string ReadEXFAT_DIRENTRY_TYPE(const EXFAT_DIRENTRY_TYPE type) { if (type == EXFAT_DIRENTRY_TYPE_END) { return "End-of-dir (00h)"; } else if (type == EXFAT_DIRENTRY_TYPE_INVALID) { return "Invalid (80h)"; } else if (type == EXFAT_DIRENTRY_TYPE_ALLOCATION_BITMAP) { return "AllocationBitmap (CritPri)"; } else if (type == EXFAT_DIRENTRY_TYPE_UPCASE_TABLE) { return "UpcaseTable (CritPri)"; } else if (type == EXFAT_DIRENTRY_TYPE_VOLUME_LABEL) { return "VolumeLabel (CritPri)"; } else if (type == EXFAT_DIRENTRY_TYPE_FILE) { return "File (CritPri)"; } else if (type == EXFAT_DIRENTRY_TYPE_VOLUME_GUID) { return "VolumeGuid (BenignPri)"; } else if (type == EXFAT_DIRENTRY_TYPE_TEXFAT_PADDING) { return "TexFATPadding (BenignPri)"; } else if (type == EXFAT_DIRENTRY_TYPE_WINDOWSCE_ACL) { return "WindowsCEAcl (BenignPri)"; } else if (type == EXFAT_DIRENTRY_TYPE_FILE_STREAM) { return "FileStream (CritSec)"; } else if (type == EXFAT_DIRENTRY_TYPE_FILE_NAME) { return "Filename (CritSec)"; } string result; if (IsDirectoryEntryUnused(type)) { SPrintf(result, "Unused (%02xh)", type); } else if (IsDirectoryEntryCritical(type) && IsDirectoryEntryPrimary(type)) { SPrintf(result, "Unknown CritPri (%02xh)", type); } else if (IsDirectoryEntryCritical(type) && IsDirectoryEntrySecondary(type)) { SPrintf(result, "Unknown CritSec (%02xh)", type); } else if (IsDirectoryEntryBenign(type) && IsDirectoryEntryPrimary(type)) { SPrintf(result, "Unknown BenignPri (%02xh)", type); } else if (IsDirectoryEntryBenign(type) && IsDirectoryEntrySecondary(type)) { SPrintf(result, "Unknown BenignSec (%02xh)", type); } else { Assert(false); // impossible to reach here } return result; } boolean IsValidExfatBootExtendedSector(const EXFAT_BOOT_EXTENDED_SECTOR & s) { return s.ExtendedBootSignature == 0xAA550000; } boolean IsValidExfatBootSector(const EXFAT_BOOT_SECTOR &boot) { // EXFAT specifies quite a bit... if (boot.EndOfSectorMarker != 0xAA55) { return false; } // shift amount can range from [0..32] for 32-bit value local const uint32 minBytes = 1 << 20; // Minimum number of bytes in exFAT volume? local const uint32 minimumSectors = IsValidBytesPerSectorShift(boot.BytesPerSectorShift) ? (1 << (20 - boot.BytesPerSectorShift)) : 1; local const uint32 bytesPerSector = IsValidBytesPerSectorShift(boot.BytesPerSectorShift) ? (1 << boot.BytesPerSectorShift) : 1; local const uint32 entriesPerFat = IsValidBytesPerSectorShift(boot.BytesPerSectorShift) ? (boot.SectorsPerFat << (boot.BytesPerSectorShift - 2)) : 1; local const uint32 lastPossibleSectorForFirstFat = boot.DataAreaSectorOffset - (boot.SectorsPerFat * boot.NumberOfFatTables); local const uint64 maximumPossibleClusters = (boot.NumberOfSectors - boot.DataAreaSectorOffset) >> boot.SectorsPerClusterShift; local int i ; // validate those MustBeZero bytes actually are zero for (i = 0; i < 53; i++) { if (boot.MustBeZero[i] != 0) { return false; } } if (boot.FileSystemRevisionMajor != 1) { //Printf("INVALID EXFAT (%08Xh): File system revision major number is not 1 (was 0x%02x)\r\n", startof(boot), boot.FileSystemRevisionMajor); return false; } if ((boot.BytesPerSectorShift < 9) || (boot.BytesPerSectorShift > 12)) { //Printf("INVALID EXFAT (%08Xh): Bytes per sector shift value invalid (0x%02x)\r\n", startof(boot)); return false; } // range from 0 .. 25-BytesPerSectorShift (32MB is maximum cluster size) if (boot.SectorsPerClusterShift > 25-boot.BytesPerSectorShift) { //Printf("INVALID EXFAT (%08Xh): SectorsPerClusterShift value (%02xh) too large for BytesPerSectorShift (%02xh)\r\n", startof(boot), boot.SectorsPerClusterShift, boot.BytesPerSectorShift); return false; } // NumberOfSectors must be at least 2^20 / 2^BytesPerSectorShift == 2 ^ ( 20-BytesPerSectorShift) if (minimumSectors > boot.NumberOfSectors) { //Printf("INVALID EXFAT (%08Xh): Volume too small (Min %08Xh sectors, but only %08Xh indicated)\r\n", startof(boot), minimumSectors, boot.NumberOfSectors); return false; } // FirstFatSectorOffset must be in range 24 .. DataAreaSectorOffset - (SectorsPerFat * NumberOfFatTables) if (boot.FirstFatSectorOffset >= boot.DataAreaSectorOffset) { //Printf("INVALID EXFAT (%08Xh): First FAT sector (%d) must be before Data Area offset (%d)\r\n", startof(boot), boot.FirstFatSectorOffset, boot.DataAreaSectorOffset); return false; } if (boot.FirstFatSectorOffset < 24) { //Printf("INVALID EXFAT (%08Xh): First FAT sector (%d) smaller than minimum (24)\r\n", startof(boot), boot.FirstFatSectorOffset); return false; } if (boot.FirstFatSectorOffset > lastPossibleSectorForFirstFat) { //Printf("INVALID EXFAT (%08Xh): First FAT sector (%d) larger than last possible (%d)\r\n", startof(boot), boot.FirstFatSectorOffset, lastPossibleSectorForFirstFat); return false; } // verify size of FAT table if (entriesPerFat - 2 < boot.CountOfDataClusters) { //Printf("INVALID EXFAT (%08Xh): Sectors per FAT (%d) can store (%d) entries, needs to store %d\r\n", startof(boot), entriesPerFat - 2, boot.CountOfDataClusters); return false; } if (boot.RootClusterIndex < 2) { //Printf("INVALID EXFAT (%08Xh): Root cluster index cannot be less than 2 (is %08Xh)\r\n", startof(boot), boot.RootClusterIndex); return false; } if (boot.RootClusterIndex > boot.CountOfDataClusters+1) { //Printf("INVALID EXFAT (%08Xh): Root cluster index cannot be more than %08Xh (is %08Xh)\r\n", startof(boot), boot.CountOfDataClusters+1, boot.RootClusterIndex); return false; } // verify NumberOfSectors against SectorsPerClusterShift, CountOfDataClusters, DataAreaSectorOffset if (boot.NumberOfSectors < boot.DataAreaSectorOffset) { //Printf("INVALID EXFAT (%08Xh): DataAreaSectorOffset (%08Xh) must be within numberOfSectors (%08Xh)\r\n", startof(boot), boot.NumberOfSectors, boot.DataAreaSectorOffset); return false; } if (maximumPossibleClusters < boot.CountOfDataClusters) { //Printf("INVALID EXFAT (%08Xh): Volume too small for cluster count (%08Xh < %08Xh)\r\n", startof(boot), maximumPossibleClusters, boot.DataAreaSectorOffset); return false; } if (boot.NumberOfFatTables != 1 && boot.NumberOfFatTables != 2) { //Printf("INVALID EXFAT (%08Xh): Number of fat tables must be 1 or 2 (was %d)\r\n", startof(boot), boot.NumberOfFatTables); return false; } if (boot.NumberOfFatTables == 1 && boot.VolumeFlags.ActiveFatIndex == 1) { //Printf("INVALID EXFAT (%08Xh): ActiveFatIndex cannot be set when only one FAT\r\n", startof(boot)); return false; } // this is mandatory... but I wonder if it's actually filled in as required? Assert(boot.OemName == "EXFAT "); return true; } boolean IsValidExfatBootRegion(const EXFAT_BOOT_REGION &boot_region) { local int i; local DWORD expected_checksum; // Don't process multiple times if (boot_region.InternallyValid) { return true; } // Boot region's internal validation... if (!boot_region.Boot.InternallyValid) { if (!IsValidExfatBootSector(boot_region.Boot)) { //Printf("INVALID EXFAT (%08Xh): Boot region boot sector internally invalid\r\n", startof(boot_region)); return false; } else { boot_region.Boot.InternallyValid = true; } } for (i = 0; i < 8; i++) { if (!boot_region.ExtendedBoot[i].InternallyValid) { //Printf("INVALID EXFAT (%08Xh): ExtendedBoot[%d] internally invalid\r\n", startof(boot_region), i); return false; } } if (!boot_region.ChecksumSector.InternallyValid) { //Printf("INVALID EXFAT (%08Xh): Boot region checksum sector internally invalid\r\n", startof(boot_region)); return false; } expected_checksum = CalculateExpectedExfatBootRegionChecksum(boot_region); if (boot_region.ChecksumSector.Checksum[0] != expected_checksum) { //Printf("INVALID EXFAT (%08Xh): Boot region checksum value (%08Xh) does not match calculated value (%08Xh)\r\n", startof(boot_region), boot_region.ChecksumSector.Checksum[0], expected_checksum); return false; } // Printf("*** VALID EXFAT BOOT REGION (%08Xh): \r\n", startof(boot_region)); return true; } boolean IsClusterIndexSeekable(const EXFAT_CLUSTER_INFO clusterIndex) { if (clusterIndex == EXFAT_CLUSTER_INVALID0 ) return false; // invalid, may indicate zero-length file, and thus a coding error if used if (clusterIndex == EXFAT_CLUSTER_INVALID1 ) return false; // definitely invalid if (clusterIndex == EXFAT_CLUSTER_BAD_CLUSTER ) return false; if (clusterIndex == EXFAT_CLUSTER_END_OF_CHAIN ) return false; if (clusterIndex > EXFAT_CLUSTER_BAD_CLUSTER ) return false; if (clusterIndex < 2 ) return false; return true; } boolean IsValidClusterIndex(const EXFAT_FAT_TABLE &fat_table, const EXFAT_CLUSTER_INFO clusterIndex) { if (! IsClusterIndexSeekable(clusterIndex)) return false; if (clusterIndex > fat_table.MaximumValidCluster ) return false; return true; } DWORD CalculateExpectedExfatBootRegionChecksumUsingStartPosition(quad start, BYTE BytesPerSectorShift) { //Printf("EXFAT (%08Xh): Calculating checksum with %d bytes per sector shift\r\n", start, BytesPerSectorShift); Assert(IsValidBytesPerSectorShift(BytesPerSectorShift)); local DWORD bytesPerSector = BytesPerSectorShiftToBytesPerSector(BytesPerSectorShift); local DWORD totalBytesToChecksum = bytesPerSector * 11; local DWORD checksum = 0; local DWORD i ; local UBYTE nextByte ; for ( i = 0; i < totalBytesToChecksum; i++ ) { if ((i != 106) && (i != 107) && (i != 112)) { nextByte = ReadByte( start + i ); // ROTATE_RIGHT + nextByte checksum = ((checksum << 31) | (checksum >> 1)) + nextByte; } } return checksum; } DWORD CalculateExpectedExfatBootRegionChecksum(const EXFAT_BOOT_REGION &boot_region) { return CalculateExpectedExfatBootRegionChecksumUsingStartPosition( startof(boot_region), boot_region.Boot.BytesPerSectorShift ); } //================================================================ // exFAT -- Unorganized from here on out :-) //================================================================ typedef struct { uint32 DoubleSeconds : 5 ; // 0..29 == 0..58 seconds uint32 Minutes : 6 ; // 0..59 uint32 Hour : 5 ; // 0..23 uint32 Day : 5 ; // 1..31 // Gregorian calendar uint32 Month : 4 ; // 1..12 // Gregorian calendar uint32 Year : 7 ; // 0..127 == 1980..2107 } EXFAT_TIMESTAMP ; string ReadEXFAT_TIMESTAMP( const EXFAT_TIMESTAMP &t ) { local string result; SPrintf( result, "%04d-%02d-%02dT%02d:%02d:%02d", t.Year + 1980, t.Month, t.Day, t.Hour, t.Minutes, t.DoubleSeconds*2 ); return result; } typedef struct (BYTE BytesPerSectorShift_Parameter) { Assert(BytesPerSectorShift_Parameter >= 9); // min 512 bytes per sector Assert(BytesPerSectorShift_Parameter <= 12); // max 4096 bytes per sector BYTE jmp[3] ; CHAR OemName[8]; BYTE MustBeZero[53] ; // covers the FAT12/16/32 BPB area uint64 NumHiddenSectors ; // aka sectors prior to this volume; ignored here; if non-zero, can be used during boot-strapping via INT 13h uint64 NumberOfSectors ; // min: 2^20 / BytesPerSectorShift max: 0xFFF..FF uint32 FirstFatSectorOffset ; // volume sector offset to first FAT table min: 24 max: complex ... uint32 SectorsPerFat ; // sector count for each copy of FAT min: (ClusterCount+2)*4 / SectorSize, ***ROUNDED UP*** max: (ClusterHeapOffset - FatOffset) / NumberOfFatTables uint32 DataAreaSectorOffset ; // volume sector offset to first data cluster uint32 CountOfDataClusters ; // stored directly in the BPB, at last! uint32 RootClusterIndex ; // first cluster, then follow FAT chain... uint32 VolumeSerialNumber ; // all values are valid byte FileSystemRevisionMinor ; byte FileSystemRevisionMajor ; struct { uint16 ActiveFatIndex : 1; // zero-based index of the active FAT uint16 IsVolumeDirty : 1; // if non-zero, volume dismounted unexpectedly uint16 MediaError : 1; // a cluster not marked as "bad" in FAT may be marginal or fail reads/writes uint16 ClearToZero : 13; // set to zero before modifying file system volume } VolumeFlags; byte BytesPerSectorShift ; // min: 9 (512 bytes), max: 12 (4096 bytes) byte SectorsPerClusterShift ; // min: 0 (1 sector/cluster), max: 25 - BytesPerSectorShift (32MB) byte NumberOfFatTables ; // min: 1, max: 2 byte DriveNumber ; // ignored here; can be used during boot-strapping via INT 13h byte PercentInUse ; // min: 0, max: 100 -OR- exactly 0xFF byte Reserved[7] ; BYTE BootCode[390] ; uint16 EndOfSectorMarker ; // BUGBUG_NOW -- Move these out of this structure, into EXFAT_DRIVE local boolean InternallyValid = 0; local uint16 BytesPerSector ; local uint16 SectorsPerCluster ; // FIX: size was only byte which could overflow local uint32 BytesPerCluster ; local uint32 MaximumValidClusterNumber; if (IsValidExfatBootSector(this)) { // and some fields whose sole purpose is simplify (duck-typing) usage by common FAT12/16/32 and exFAT code BytesPerSector = 1 << BytesPerSectorShift; SectorsPerCluster = 1 << SectorsPerClusterShift; MaximumValidClusterNumber = CountOfDataClusters + 1; BytesPerCluster = BytesPerSector; BytesPerCluster *= SectorsPerCluster; if (BytesPerSectorShiftToBytesPerSector(BytesPerSectorShift_Parameter) > 512) { BYTE Undefined[BytesPerSectorShiftToBytesPerSector(BytesPerSectorShift_Parameter) - 512]; } InternallyValid = 1; // only set this after checked validity } } EXFAT_BOOT_SECTOR ; typedef struct (BYTE BytesPerSectorShift) { BYTE ExtendedBootCode[ BytesPerSectorShiftToBytesPerSector(BytesPerSectorShift)-4 ]; DWORD ExtendedBootSignature; // BUGBUG_NOW -- Move out of this structure, into EXFAT_DRIVE local int InternallyValid = (ExtendedBootSignature == 0xAA550000); // TODO: remove this local variable entirely } EXFAT_BOOT_EXTENDED_SECTOR ; typedef struct { GUID ParameterGuid; UBYTE CustomDefined[32]; // meaning depends upon the GUID } EXFAT_OEM_PARAMETER_TEMPLATE ; // OEM Flash Parameters from: http://www.ntfs.com/exfat-overview.htm typedef struct { GUID ParameterGuid; // {0A0C7E46-3399-4021-90C8-FA6D389C4BA2} UINT32 EraseBlockSize; // erase block size in bytes UINT32 PageSize; // ??? what page type ??? UINT32 NumberOfSpareBlocks; // ??? count in units of EraseBlockSize or PageSize or ??? UINT32 tRandomAccess_ns; // Random access time in nanoseconds UINT32 tProgram_ns; // Program time in nanoseconds UINT32 tReadCycle_ns; // Serial Read cycle time in nanoseconds UINT32 tWriteCycle_ns; // Write cycle time in nanoseconds UBYTE Reserved[4]; } EXFAT_OEM_FLASH_PARAMETER; boolean IsGuidExfatOemFlashParameter( const GUID& g ) { return (0x467E0C0A == g.Part1 ) && ( 0x9933 == g.Part2 ) && ( 0x2140 == g.Part3 ) && ( 0x90 == g.Part4[0]) && ( 0xC8 == g.Part4[1]) && ( 0xFA == g.Part5[0]) && ( 0x6D == g.Part5[1]) && ( 0x38 == g.Part5[2]) && ( 0x9C == g.Part5[3]) && ( 0x4B == g.Part5[4]) && ( 0xA2 == g.Part5[5]) ; } typedef struct (BYTE BytesPerSectorShift) { // regardless of the BytesPerSectorShift, only ten (10) OEM_PARAMETER_TEMPLATE are stored... local int i ; for (i = 0; i < 10; i++) { // TODO: read-ahead the GUID of the EXFAT_OEM_PARAMETER_TEMPLATE, and based on its value, // instantiate pre-defined types. There's only one type at the moment... EXFAT_OEM_PARAMETER_TEMPLATE Parameter; } UBYTE Reserved[ BytesPerSectorShiftToBytesPerSector(BytesPerSectorShift) - 480 ]; } EXFAT_BOOT_OEM_PARAMETER_SECTOR ; typedef struct (BYTE BytesPerSectorShift) { UBYTE Reserved[ BytesPerSectorShiftToBytesPerSector(BytesPerSectorShift) ]; } EXFAT_BOOT_RESERVED_SECTOR; typedef struct (BYTE BytesPerSectorShift) { DWORD Checksum[BytesPerSectorShiftToBytesPerSector(BytesPerSectorShift) / 4]; // BUGBUG_NOW -- Move these out of this structure, into EXFAT_DRIVE or support function local boolean InternallyValid; // prevents array optimization ... but not needed for this sector anyways local quad start; // prevents array optimization ... but not needed for this sector anyways local DWORD checksumCopies ; // ok for optimization, as does not depend on instance data local DWORD i ; // ok for optimization, as does not depend on instance data InternallyValid = 1; start = FTell(); checksumCopies = BytesPerSectorShiftToBytesPerSector(BytesPerSectorShift) / 4; for (i = 0; InternallyValid && i < checksumCopies; i++) { if (Checksum[0] != Checksum[i]) { //Printf("INVALID EXFAT (%08Xh): Checksum sector value differs at index %xh, value %08Xh != %08Xh\r\n", start, i, Checksum[0], Checksum[i]); InternallyValid = 0; } } } EXFAT_BOOT_CHECKSUM_SECTOR ; typedef struct (BYTE BytesPerSectorShift) { Assert(BytesPerSectorShift >= 9); // min 512 bytes per sector Assert(BytesPerSectorShift <= 12); // max 4096 bytes per sector EXFAT_BOOT_SECTOR Boot(BytesPerSectorShift); // BUGBUG_NOW -- Move these out of this structure, into EXFAT_DRIVE or support function local quad start; // prevents array optimization local boolean InternallyValid; // prevents array optimization local DWORD i ; start = FTell(); InternallyValid = 0; // BUGBUG_NOW -- Move these out of this structure, into EXFAT_DRIVE or support function for (i = 0; i < 8; i++) { EXFAT_BOOT_EXTENDED_SECTOR ExtendedBoot(BytesPerSectorShift); } EXFAT_BOOT_OEM_PARAMETER_SECTOR OemParameters(BytesPerSectorShift); // no validation here EXFAT_BOOT_RESERVED_SECTOR ReservedSector(BytesPerSectorShift); // no validation here EXFAT_BOOT_CHECKSUM_SECTOR ChecksumSector(BytesPerSectorShift); } EXFAT_BOOT_REGION ; // TODO -- Waiting on bugfix -- enable on-demand ,size=BytesPerSectorShiftToBytesPerSector(bps_shift)>; typedef struct (quad CountOfDataClusters) { // Then seek back to start, so FAT array indices correspond to stored values in the FAT chain EXFAT_MEDIA_TYPE MediaType; // legacy: FAT index 0 was used for media type // legacy: FAT index 1 was used in prior FS, but is not used in exFAT FSkip( -4 ); EXFAT_CLUSTER_INFO Cluster[ CountOfDataClusters + 2]; local DWORD MaximumValidCluster = CountOfDataClusters + 1; // this is not dependent upon instance data, so OK to optimize } EXFAT_FAT_TABLE; // TODO -- Waiting on bugfix -- enable on-demand ,size=BytesPerSectorShiftToBytesPerSector(bps_shift)>; // zero-size structure warning... oh, well. typedef struct ( QUAD ClusterCount ) { EXFAT_CLUSTER_INFO ClusterIndex; local QUAD CountOfClusters = ClusterCount; // Not dependent on instance data, so OK to optimize } EXFAT_EXTENT ; string ReadEXFAT_EXTENT(const EXFAT_EXTENT& e) { local string result; if (e.CountOfClusters == 1) { SPrintf(result, "%d", e.ClusterIndex); } else { SPrintf(result, "%d (%d clusters)", e.ClusterIndex, e.CountOfClusters); } return result; } // Given a starting cluster index (at current file position), create an array of extents for the file data typedef struct ( int DriveNum, EXFAT_CLUSTER_INFO startingClusterIndex, QUAD contiguousClusters ) { // using a function removes local variables without having to declare every one as "hidden". local DWORD TotalClusterCount; TotalClusterCount = GenerateChainedExtent( drive[DriveNum].ActiveFat, startingClusterIndex, contiguousClusters); } EXFAT_CHAINED_EXTENTS ; // struct jumps around the file, so does not support optimized array string ReadEXFAT_CHAINED_EXTENTS(const EXFAT_CHAINED_EXTENTS &e) { local string result; local int i; if (!exists(e.end_of_chain)) { result += ReadEXFAT_EXTENT(e.extent[0]); result += " (contig)"; } else { for (i = 0; exists(e.extent[i]); i++) { result += ReadEXFAT_EXTENT(e.extent[i]); result += "->"; } result += "eoc"; } return result; } DWORD GenerateChainedExtent(const EXFAT_FAT_TABLE &fat_table, EXFAT_CLUSTER_INFO startingClusterIndex, QUAD contiguousClusters ) { local EXFAT_CLUSTER_INFO currentClusterIndex = startingClusterIndex; local quad start = FTell(); local DWORD TotalClusterCount = 0; local DWORD index = 0; local DWORD q; // for loop detection local DWORD tortoise; // for loop detection local DWORD hare; // for loop detection local DWORD loopLength; // for loop detection local DWORD loopStartIndex; // for loop detection Assert(IsValidClusterIndex(fat_table, startingClusterIndex)); // starting cluster must always be valid data, else not create this! Assert(ReadUInt() == startingClusterIndex); // startingClusterIndex must == current position value, *** even if not stored in the FAT table *** // handle easy case of contiguous file entry first (no fat entries needed) if (0 != contiguousClusters) { EXFAT_EXTENT extent(contiguousClusters); TotalClusterCount = contiguousClusters; FSeek(start+4); return TotalClusterCount; } while (IsValidClusterIndex(fat_table, currentClusterIndex)) { // keep it simple for now... non-contiguous just gets one entry per cluster EXFAT_EXTENT extent(1); // Loop detection -- use tortoise and hare algorithm during the build to detect if (1 == (index % 2)) { if (extent[index].ClusterIndex == extent[index/2].ClusterIndex) { // if hare == tortoise, a loop exists // Detect size of the loop: tortoise = index/2; hare = tortoise + 1; while (extent[tortoise].ClusterIndex != extent[hare].ClusterIndex) { hare++; } loopLength = hare - tortoise; // Detect start of the loop: tortoise = 0; hare = loopLength; while (extent[tortoise].ClusterIndex != extent[hare].ClusterIndex) { hare++; tortoise++; } loopStartIndex = tortoise; // don't print more than needed... index = loopStartIndex + loopLength + 1; Printf("Loop detected in cluster chain:\r\n"); for (q = 0; q < loopStartIndex; q++) { Printf("%d -> ", extent[q].ClusterIndex); } Printf(" [[ "); for (true; q < (index-1); q++) { Printf("%d -> ", extent[q].ClusterIndex); } Printf("%d ... ]]\r\n", extent[q].ClusterIndex); FSeek(start+4); return 0; // LOOP ... try to avoid using this chain ... } } TotalClusterCount += 1; // move file to that cluster index in the FAT... FSeek(startof(fat_table.Cluster[currentClusterIndex])); // and then determine what that next index value is... currentClusterIndex = fat_table.Cluster[currentClusterIndex]; } // Add one more extent to store the "end of chain" mark... mostly for debugging FAT chain // Give it a different name so can easily loop through all extent[] array entries. EXFAT_CLUSTER_INFO end_of_chain; // hack to avoid error that ending position was in front of starting position // NOTE: THIS STRUCTURE SHOULD BE ABLE TO SPECIFY ATTRIBUTE , but blocked by bug (as of v9.0c) FSeek(start+4); return TotalClusterCount; } typedef struct { EXFAT_DIRENTRY_TYPE EntryType ; FSkip(-1); UBYTE TypeCode : 5; UBYTE Benign : 1; UBYTE Secondary : 1; UBYTE InUse : 1; UBYTE CustomDefined[19] ; DWORD FirstClusterIndex ; UQUAD DataLength ; } EXFAT_DIRENTRY_GENERIC ; wstring ReadEXFAT_DIRENTRY_GENERIC( const EXFAT_DIRENTRY_GENERIC &e ) { return ReadEXFAT_DIRENTRY_TYPE(e.EntryType); } typedef struct { EXFAT_DIRENTRY_TYPE EntryType; // 0 == EXFAT_DIRENTRY_TYPE_END (0x81) FSkip(-1); UBYTE TypeCode : 5; UBYTE Benign : 1; UBYTE Secondary : 1; UBYTE InUse : 1; UBYTE Undefined[31]; // 1 .. 31 } EXFAT_DIRENTRY_END ; string ReadEXFAT_DIRENTRY_END( const EXFAT_DIRENTRY_END &e ) { return "** EndOfDirectory Marker"; } typedef struct { EXFAT_DIRENTRY_TYPE EntryType; // 0 == EXFAT_DIRENTRY_TYPE_END (0x81) FSkip(-1); UBYTE TypeCode : 5; UBYTE Benign : 1; UBYTE Secondary : 1; UBYTE InUse : 1; UBYTE Undefined[31]; // 1 .. 31 } EXFAT_DIRENTRY_UNUSED ; string ReadEXFAT_DIRENTRY_UNUSED( const EXFAT_DIRENTRY_UNUSED &e ) { return "** Unused"; } typedef struct { EXFAT_DIRENTRY_TYPE EntryType; // 0 FSkip(-1); UBYTE TypeCode : 5; UBYTE Benign : 1; UBYTE Secondary : 1; UBYTE InUse : 1; UBYTE SecondaryEntriesCount; // 1 USHORT EntrySetChecksum; // 2..3 USHORT AllocationPossible : 1; USHORT NoFatChain : 1; USHORT _ : 14; UBYTE CustomDefined[14]; // 6..19 -- flags and other stuff? DWORD FirstCluster; // 20..23 UQUAD DataLength; // 24..31 } EXFAT_DIRENTRY_GENERIC_PRIMARY ; wstring ReadEXFAT_DIRENTRY_GENERIC_PRIMARY( const EXFAT_DIRENTRY_GENERIC_PRIMARY &e ) { return ReadEXFAT_DIRENTRY_TYPE(e.EntryType); } typedef struct { EXFAT_DIRENTRY_TYPE EntryType; // 0 FSkip(-1); UBYTE TypeCode : 5; UBYTE Benign : 1; UBYTE Secondary : 1; UBYTE InUse : 1; UBYTE AllocationPossible : 1; // 1 -- flags UBYTE NoFatChain : 1; UBYTE _ : 6; UBYTE CustomDefined[18]; // 2..19 -- flags and other stuff? DWORD FirstCluster; // 20..23 UQUAD DataLength; // 24..31 } EXFAT_DIRENTRY_GENERIC_SECONDARY ; wstring ReadEXFAT_DIRENTRY_GENERIC_SECONDARY( const EXFAT_DIRENTRY_GENERIC_SECONDARY &e ) { return ReadEXFAT_DIRENTRY_TYPE(e.EntryType); } typedef struct { EXFAT_DIRENTRY_TYPE EntryType; // 0 == EXFAT_DIRENTRY_TYPE_ALLOCATION_BITMAP (0x81) FSkip(-1); UBYTE TypeCode : 5; UBYTE Benign : 1; UBYTE Secondary : 1; UBYTE InUse : 1; UBYTE BitmapIndex : 1; // 1 .. redefines SecondaryEntriesCount UBYTE _ : 7; UBYTE Reserved[18]; // 2..19 -- flags and other stuff? DWORD FirstCluster; // 20..23 UQUAD DataLength; // 24..31 } EXFAT_DIRENTRY_ALLOCATION_BITMAP ; wstring ReadEXFAT_DIRENTRY_ALLOCATION_BITMAP( const EXFAT_DIRENTRY_ALLOCATION_BITMAP &e ) { local string result; if (e.EntryType == EXFAT_DIRENTRY_TYPE_ALLOCATION_BITMAP) { SPrintf(result, "** Allocation Bitmap #%s", e.BitmapIndex ? "1" : "0"); } else { result = ReadEXFAT_DIRENTRY_TYPE(e.EntryType); } return result; } typedef struct { EXFAT_DIRENTRY_TYPE EntryType; // 0 == EXFAT_DIRENTRY_TYPE_UPCASE_TABLE (0x82) FSkip(-1); UBYTE TypeCode : 5; UBYTE Benign : 1; UBYTE Secondary : 1; UBYTE InUse : 1; UBYTE _[3]; // 1.. 3 // SecondaryEntriesCount, checksum DNE DWORD TableChecksum; // 4.. 7 // Different checksum than normal primary UBYTE __[12]; // 8..19 DWORD FirstCluster; // 20..23 UQUAD DataLength; // 24..31 } EXFAT_DIRENTRY_UPCASE_TABLE ; wstring ReadEXFAT_DIRENTRY_UPCASE_TABLE( const EXFAT_DIRENTRY_UPCASE_TABLE &e ) { return ReadEXFAT_DIRENTRY_TYPE(e.EntryType); } typedef struct { EXFAT_DIRENTRY_TYPE EntryType; // 0 == EXFAT_DIRENTRY_TYPE_VOLUME_LABEL (0x83) FSkip(-1); UBYTE TypeCode : 5; UBYTE Benign : 1; UBYTE Secondary : 1; UBYTE InUse : 1; UBYTE CharacterCount; // 1 wchar_t Label[11]; // 2..23 UBYTE Reserved[8]; // 24..31 -- normally DataLength } EXFAT_DIRENTRY_VOLUME_LABEL ; wstring ReadEXFAT_DIRENTRY_VOLUME_LABEL( const EXFAT_DIRENTRY_VOLUME_LABEL &e ) { local wstring result; if (e.EntryType == EXFAT_DIRENTRY_TYPE_VOLUME_LABEL) { WStrncpy(result, e.Label, 11); } else { result = ReadEXFAT_DIRENTRY_TYPE(e.EntryType); } return result; } typedef struct { EXFAT_DIRENTRY_TYPE EntryType; // 0 == EXFAT_DIRENTRY_TYPE_FILE (0x85) FSkip(-1); UBYTE TypeCode : 5; UBYTE Benign : 1; UBYTE Secondary : 1; UBYTE InUse : 1; UBYTE SecondaryEntriesCount; // 1 USHORT EntrySetChecksum; // 2..3 struct { USHORT ReadOnly : 1; USHORT Hidden : 1; USHORT System : 1; USHORT Reserved1 : 1; USHORT Directory : 1; USHORT Archive : 1; USHORT Reserved2 : 10; } Attributes; BYTE Reserved1[2]; // 6..7 EXFAT_TIMESTAMP Created; // 8..11 EXFAT_TIMESTAMP Modified; // 12..15 EXFAT_TIMESTAMP Accessed; // 16..19 UBYTE Created10msIncrement; // 20 UBYTE Modified10msIncrement; // 21 UBYTE __[3]; // 22..24 -- three identical bytes... UBYTE Reserved2[7]; // 25..31 } EXFAT_DIRENTRY_FILE ; wstring ReadEXFAT_DIRENTRY_FILE( const EXFAT_DIRENTRY_FILE &e ) { local string result; // cmd.exe shows as "A SHR" if (e.EntryType == EXFAT_DIRENTRY_TYPE_FILE) { result += e.Attributes.Directory ? "D" : "."; result += e.Attributes.Archive ? "A" : "."; result += e.Attributes.System ? "S" : "."; result += e.Attributes.Hidden ? "H" : "."; result += e.Attributes.ReadOnly ? "R" : "."; } else { result = ReadEXFAT_DIRENTRY_TYPE(e.EntryType); } return result; } typedef struct { EXFAT_DIRENTRY_TYPE EntryType; // 0 == EXFAT_DIRENTRY_TYPE_FILE_STREAM (0xA0) FSkip(-1); UBYTE TypeCode : 5; UBYTE Benign : 1; UBYTE Secondary : 1; UBYTE InUse : 1; UBYTE AllocationPossible : 1; // 1 == Flags UBYTE NoFatChain : 1; UBYTE _; // 2 -- Reserved1 UBYTE NameCharCount; // 3 -- characters in UTF16 string UINT16 NameHash; // 4,5 -- hash of up-cased filename UINT16 __; // 6,7 -- Reserved2 UQUAD ValidDataLength; // 8..15 -- only this many bytes of file valid UINT32 ___; // 16..19 -- Reserved3 DWORD FirstCluster; // 20..23 UQUAD DataLength; // 24..31 } EXFAT_DIRENTRY_FILE_STREAM ; wstring ReadEXFAT_DIRENTRY_FILE_STREAM( const EXFAT_DIRENTRY_FILE_STREAM &e ) { local string result; if (e.EntryType == EXFAT_DIRENTRY_TYPE_FILE_STREAM) { result += e.AllocationPossible ? "" : "(no data)"; result += e.NoFatChain ? "" : "(contig)"; } else { result = ReadEXFAT_DIRENTRY_TYPE(e.EntryType); } return result; } typedef struct { EXFAT_DIRENTRY_TYPE EntryType; // 0 == EXFAT_DIRENTRY_TYPE_FILE_NAME (0xA1) FSkip(-1); UBYTE TypeCode : 5; UBYTE Benign : 1; UBYTE Secondary : 1; UBYTE InUse : 1; UBYTE AllocationPossible : 1; // 1 == Flags UBYTE NoFatChain : 1; wchar_t NameCharacters[15]; } EXFAT_DIRENTRY_FILE_NAME ; wstring ReadEXFAT_DIRENTRY_FILE_NAME( const EXFAT_DIRENTRY_FILE_NAME &e ) { local wstring result; if (e.EntryType == EXFAT_DIRENTRY_TYPE_FILE_NAME) { WStrncpy(result, e.NameCharacters, 11); } else { result = ReadEXFAT_DIRENTRY_TYPE(e.EntryType); } return result; } typedef struct { EXFAT_DIRENTRY_TYPE EntryType; // 0 == EXFAT_DIRENTRY_TYPE_VOLUME_GUID (0xC0) FSkip(-1); UBYTE TypeCode : 5; UBYTE Benign : 1; UBYTE Secondary : 1; UBYTE InUse : 1; UBYTE SecondaryEntriesCount; // 1 USHORT SetChecksum; // 2..3 USHORT AllocationPossible : 1; USHORT NoFatChain : 1; USHORT Reserved1 : 14; GUID VolumeGuid; // 6..21 BYTE Reserved2[10]; // 22..31 } EXFAT_DIRENTRY_VOLUME_GUID ; wstring ReadEXFAT_DIRENTRY_VOLUME_GUID( const EXFAT_DIRENTRY_VOLUME_GUID &e ) { local string result; if (e.EntryType == EXFAT_DIRENTRY_TYPE_VOLUME_GUID) { result = ReadGUID(e.VolumeGuid); } else { result = ReadEXFAT_DIRENTRY_TYPE(e.EntryType); } return result; } typedef EXFAT_DIRENTRY_GENERIC_PRIMARY EXFAT_DIRENTRY_TEXFAT_PADDING; typedef struct(int DriveNumber) { EXFAT_DIRENTRY_ALLOCATION_BITMAP BitmapEntry; local quad end = FTell(); FSeek(startof(BitmapEntry.FirstCluster)); EXFAT_DATA BitmapData(DriveNumber, BitmapEntry.FirstCluster, false, BitmapEntry.DataLength); FSeek(end); } EXFAT_ALLOCATION_BITMAP ; typedef struct(int DriveNumber) { EXFAT_DIRENTRY_UPCASE_TABLE UpcaseTableEntry; local quad end = FTell(); FSeek(startof(UpcaseTableEntry.FirstCluster)); EXFAT_DATA BitmapData(DriveNumber, UpcaseTableEntry.FirstCluster, false, UpcaseTableEntry.DataLength); FSeek(end); } EXFAT_UPCASE_TABLE ; // TODO: EXFAT_DIRENTRY_WINDOWSCE_ACL // == EXFAT_DIRENTRY_TYPE_WINDOWSCE_ACL (0xC2) quad GetFilePositionForCluster(int DriveNumber, EXFAT_CLUSTER_INFO clusterIndex) { local quad result = drive[DriveNumber].filePositionForFirstCluster; result += drive[DriveNumber].bytesPerCluster * (clusterIndex - 2); return result; } typedef struct (DWORD byteCount) { byte Data[byteCount]; } EXFAT_BUFFER; void exfat_parse_extents_as_file_data(int DriveNumber, const EXFAT_CHAINED_EXTENTS &e, UQUAD ValidDataLength) { local DWORD bytesPerCluster = drive[DriveNumber].bytesPerCluster; local UQUAD remainingValidBytes = ValidDataLength; local int i = 0; local UQUAD bytesThisExtent; // first ValidDataLength bytes will all be added as file data. if (ValidDataLength == 0) { return; } // if there are no clusters, then there is nothing to show.... if (e.TotalClusterCount == 0) { return; } while (exists(e.extent[i])) { FSeek(GetFilePositionForCluster(DriveNumber, e.extent[i].ClusterIndex)); bytesThisExtent = e.extent[i].CountOfClusters * bytesPerCluster; if (remainingValidBytes > bytesThisExtent) { EXFAT_BUFFER Valid(bytesThisExtent); remainingValidBytes -= bytesThisExtent; } else if (remainingValidBytes > 0) { EXFAT_BUFFER Valid(remainingValidBytes); if (remainingValidBytes < bytesThisExtent) { EXFAT_BUFFER UnusedAllocated(bytesThisExtent - remainingValidBytes); } remainingValidBytes = 0; } else { EXFAT_BUFFER UnusedAllocated(bytesThisExtent); } i++; } } void exfat_add_invalid_directory_entry(const quad position) { local EXFAT_DIRENTRY_TYPE type = ReadUByte(position); FSeek(position); if (IsDirectoryEntryPrimary(type)) { if (type == EXFAT_DIRENTRY_TYPE_ALLOCATION_BITMAP) { EXFAT_DIRENTRY_ALLOCATION_BITMAP InvalidEntry; } else if (type == EXFAT_DIRENTRY_TYPE_UPCASE_TABLE) { EXFAT_DIRENTRY_UPCASE_TABLE InvalidEntry; } else if (type == EXFAT_DIRENTRY_TYPE_VOLUME_LABEL) { EXFAT_DIRENTRY_VOLUME_LABEL InvalidEntry; } else if (type == EXFAT_DIRENTRY_TYPE_FILE) { EXFAT_DIRENTRY_FILE InvalidEntry; } else if (type == EXFAT_DIRENTRY_TYPE_VOLUME_GUID) { EXFAT_DIRENTRY_VOLUME_GUID InvalidEntry; } else if (type == EXFAT_DIRENTRY_TYPE_TEXFAT_PADDING) { EXFAT_DIRENTRY_TEXFAT_PADDING InvalidEntry; } else { EXFAT_DIRENTRY_GENERIC_PRIMARY InvalidEntry; } } else { if (type == EXFAT_DIRENTRY_TYPE_FILE_STREAM) { EXFAT_DIRENTRY_FILE_STREAM InvalidEntry; } else if (type == EXFAT_DIRENTRY_TYPE_FILE_NAME) { EXFAT_DIRENTRY_FILE_NAME InvalidEntry; } else { EXFAT_DIRENTRY_GENERIC_SECONDARY InvalidEntry; } } return; } typedef struct (int DriveNum, const quad positions[], EXFAT_DIRENTRY_TYPE types[], uint16 secondaryEntryCount) { local int DriveNumber = DriveNum; local int i ; local wstring Filename; EXFAT_DIRENTRY_FILE FileEntry; for (i = 1; i < secondaryEntryCount+1; i++) { if (types[i] == EXFAT_DIRENTRY_TYPE_FILE_STREAM) { EXFAT_DIRENTRY_FILE_STREAM StreamEntry; // should be only one... } else if (types[i] == EXFAT_DIRENTRY_TYPE_FILE_NAME) { EXFAT_DIRENTRY_FILE_NAME NameEntry; // could be many... } else { EXFAT_DIRENTRY_GENERIC_SECONDARY UnrecognizedEntry; } } local quad endPos = FTell(); Assert(exists(this.NameEntry[0])); Assert(exists(this.StreamEntry[0])); FSeek( startof(this.StreamEntry[0].ValidDataLength) ); UQUAD ValidDataLength; // Now parse the name of the file... Filename = ""; for (i = 0; exists(this.NameEntry[i]); i++) { Filename += NameEntry[i].NameCharacters; } if (WStrlen(Filename) > StreamEntry[0].NameCharCount) { Filename = WSubStr(Filename, StreamEntry[0].NameCharCount); } // for non-zero size allocated (even if zero bytes valid), // show the allocated buffers if (StreamEntry[0].DataLength != 0) { if (FileEntry.Attributes.Directory) { FSeek( startof(this.StreamEntry[0].FirstCluster) ); EXFAT_DIRECTORY_CONTENTS Contents( DriveNumber, StreamEntry[0].FirstCluster, StreamEntry[0].NoFatChain, StreamEntry[0].DataLength ); } else { FSeek( startof(StreamEntry[0].FirstCluster) ); EXFAT_DATA File( DriveNumber, StreamEntry[0].FirstCluster, StreamEntry[0].NoFatChain, StreamEntry[0].ValidDataLength ); } } FSeek(endPos); } EXFAT_FILE ; string ReadEXFAT_FILE( const EXFAT_FILE &e ) { string result; // show attributes + filename + size SPrintf(result, "%s %s %d bytes", ReadEXFAT_DIRENTRY_FILE(e.FileEntry[0]), e.Filename, e.StreamEntry[0].ValidDataLength); return result; } // REQUIRES: first entry is primary, all remaining entries are secondary typedef struct (int DriveNum, const quad positions[], EXFAT_DIRENTRY_TYPE types[], uint16 secondaryEntryCount) { local int startPos = FTell(); local int DriveNumber = DriveNum; local int i ; //Printf("Creating EXFAT_DIRENTRY_SET with %d secondary entries\r\n", secondaryEntryCount); FSeek(positions[0]); EXFAT_DIRENTRY_GENERIC_PRIMARY GenericPrimaryEntry; if ((GenericPrimaryEntry.AllocationPossible) && (GenericPrimaryEntry.DataLength != 0)) { FSeek( startof( GenericPrimaryEntry.FirstCluster) ); EXFAT_DATA GenericPrimaryData( DriveNumber, GenericPrimaryEntry.FirstCluster, // first cluster index GenericPrimaryEntry.NoFatChain, GenericPrimaryEntry.DataLength ); } FSeek(startPos + 32); for (i = 1; i < secondaryEntryCount+1; i++) { EXFAT_DIRENTRY_GENERIC_SECONDARY UnrecognizedEntry; } return; } EXFAT_DIRENTRY_SET ; // QWERTY - Change this into unique Read* functions, one per known primary type wstring ReadEXFAT_DIRENTRY_SET( const EXFAT_DIRENTRY_SET &e ) { string result; if (exists(e.GenericPrimaryEntry.Typecode)) { SPrintf(result, "** unrecognized - %s **", ReadEXFAT_DIRENTRY_TYPE(e.GenericPrimaryEntry.Typecode)); } return result; } // take a set of exfat_direntry_* entry positions, and covert into a appropriate higher-level types void exfat_parse_directory_entry_set(int DriveNum, const quad positions[], uint16 validPositionCount) { local int DriveNumber = DriveNum; local boolean processedCount = 0; // callers are expected to call this routine with: // { UNUSED_ENTRY } // unused entry, one at a time // { SEC+ } // corrupt media, but still should expose them // { PRI SEC* } // a primary and up to 255 secondary // in the last case, there may be more or fewer SECs than are needed by the PRI // // positions == array of file positions, one per directory entry, potentially across clusters // validPositionCount == number of directory entries that can be parsed // //Printf("Parsing potential directory entry set (%d entries)\r\n", validPositionCount); Assert(validPositionCount >= 1); Assert(validPositionCount <= 256); // first, read the type for each entry to be parsed... local EXFAT_DIRENTRY_TYPE types[validPositionCount]; local uint16 requiredSecondaryEntries; local uint16 calculatedChecksum; local uint16 actualChecksum; local int i ; for (i = 0; i < validPositionCount; i++) { types[i] = ReadUByte(positions[i]); //Printf(" direntry at %08xh, type == %02x (%s)\r\n", positions[i], types[i], ReadEXFAT_DIRENTRY_TYPE(types[i]) ); } // a failure here is a coding error in the splitting of entries for parsing for (i = 1; i < validPositionCount; i++) { Assert(IsDirectoryEntrySecondary(types[i])); } FSeek(positions[0]); switch(types[0]) { case EXFAT_DIRENTRY_TYPE_END: // end-of-directory is always exactly one entry, regardless of // the other bytes in the entry... Assert(validPositionCount == 1); EXFAT_DIRENTRY_END EoD; processedCount = 1; break; case EXFAT_DIRENTRY_TYPE_ALLOCATION_BITMAP: EXFAT_ALLOCATION_BITMAP AllocationBitmap(DriveNum); processedCount = 1; break; case EXFAT_DIRENTRY_TYPE_VOLUME_LABEL: EXFAT_DIRENTRY_VOLUME_LABEL VolumeLabel; processedCount = 1; break; case EXFAT_DIRENTRY_TYPE_TEXFAT_PADDING: EXFAT_DIRENTRY_TEXFAT_PADDING Padding; processedCount = 1; break; case EXFAT_DIRENTRY_TYPE_VOLUME_GUID: EXFAT_DIRENTRY_VOLUME_GUID VolumeGuid; processedCount = 1; break; case EXFAT_DIRENTRY_TYPE_UPCASE_TABLE: EXFAT_UPCASE_TABLE UpcaseTable(DriveNumber); processedCount = 1; break; case EXFAT_DIRENTRY_TYPE_FILE: // somewhat more complex... requiredSecondaryEntries = ReadUByte(positions[0]+1); if (requiredSecondaryEntries > (validPositionCount-1)) { // not enough entries break; } if (types[1] != EXFAT_DIRENTRY_TYPE_FILE_STREAM) { // FILE_STREAM entry is necessary break; } if (types[2] != EXFAT_DIRENTRY_TYPE_FILE_NAME) { // FILE requires STREAM followed by 1+ FILE_NAME break; } if (ReadUByte(positions[0]+4) & 0x10) { // then it's a directory EXFAT_FILE Directory(DriveNumber, positions, types, requiredSecondaryEntries); } else { EXFAT_FILE File(DriveNumber, positions, types, requiredSecondaryEntries); } processedCount = requiredSecondaryEntries + 1; break; default: if (IsDirectoryEntryUnused(types[0])) { EXFAT_DIRENTRY_UNUSED Unused; processedCount = 1; break; } if (IsDirectoryEntryBenignPrimary(types[0])) { requiredSecondaryEntries = ReadUByte(positions[0]+1); if (requiredSecondaryEntries > (validPositionCount-1)) { // not enough entries break; } // QWERTY - change to first-class structure EXFAT_BENIGN_PRIMARY EXFAT_DIRENTRY_SET BenignPrimarySet; processedCount = 1; break; } // else it's not known entry, not benign primary, not unused, and not end-of-directory... // that makes it invalid... break; } // if processed at least one entry, any contiguous benign secondary entries are allowed. while ((processedCount != 0) && (processedCount < validPositionCount) && IsDirectoryEntryBenignSecondary(types[processedCount])) { // QWERTY - make this a struct all its own... EXFAT_DIRENTRY_GENERIC_SECONDARY AssociatedBenignSecondary; processedCount++; } // any remaining entries are invalid. for (i = processedCount; i < validPositionCount; i++) { // only known secondary types are FILE_STREAM and FILE_NAME exfat_add_invalid_directory_entry(positions[i]); } return; } uint16 exfat_calculate_entry_set_checksum(const quad positions[], uint16 validPositionCount) { if (validPositionCount == 0) return; Assert(validPositionCount <= 256); local uint16 entriesToValidate = validPositionCount; local uint16 checksum = 0; local uint16 highbit = 0x8000; local uint16 i; local uint16 j; local quad currentPosition = positions[0]; // first entry is special, because must exclude the checksum itself for (i = 0; i < 20; i++) { if ((2 == i) || (3 == i)) { continue; } // rotate right + next byte checksum = ((checksum & 1) ? highbit : 0) + ReadByte(currentPosition+i); } // remaining entries process all bytes for (j = 1; j < entriesToValidate; j++) { currentPosition = positions[j]; for (i = 0; i < 20; i++) { // rotate right + next byte checksum = ((checksum & 1) ? highbit : 0) + ReadByte(currentPosition+i); } } return checksum; } // this function parses the extents into groups that start with // a primary entry + up to 255 following secondary entries // the positions of those (up to 256 total entries) is then provided // to exfat_parse_directory_entry_set() for additional parsing. // // QWERTY - change this to name exfat_split_extents() to indicate // its primary purpose is to split (not interpret) the // extents at each primary entry. void exfat_parse_extents_as_directory(int DriveNumber, const EXFAT_CHAINED_EXTENTS &e) { local DWORD bytesPerCluster = drive[DriveNumber].bytesPerCluster; local DWORD entriesPerCluster = bytesPerCluster/0x20; // a Crit Pri may have up to 255 following secondaries // It's a real pain to have every function check offsets // so cache them here, then pass the array and secondary // count to routines that must create structures, calculate // checksums, etc. local quad positions[256]; local int eod = 0; // set to 1 when when hit end-of-directory local int idxExtent = 0; // which extent is being parsed.... local int idxCurrentCluster = 0; // index of entry in current cluster local int cntEntriesCurrentExtent; // how many entries in current extent local int validPositionCount = 0; // how many already have for the set? local quad tmpPos; local EXFAT_DIRENTRY_TYPE tmpEntry; // option 1: repeat logic to get next cluster entry many times // option 2: loop through cluster entries, keep state // option 3: loop and split on primary or unused entries, // but parse in second routine // I'm going with option 3... for (idxExtent=0; exists(e.extent[idxExtent]) && !eod; idxExtent++) { // just update index cntEntriesCurrentExtent = e.extent[idxExtent].CountOfClusters * entriesPerCluster; idxCurrentCluster = 0; tmpPos = GetFilePositionForCluster(DriveNumber, e.extent[idxExtent].ClusterIndex); // iterate through all the entries for ( idxCurrentCluster = 0; (!eod) && (idxCurrentCluster < cntEntriesCurrentExtent); idxCurrentCluster++) { // read the entry tmpEntry = ReadUByte(tmpPos); // if the entry is unused or primary, parse what's already in queue // also do this if too many secondary entries... if (validPositionCount > 0 && (IsDirectoryEntryUnused(tmpEntry) || IsDirectoryEntryPrimary(tmpEntry) || validPositionCount == 256 )) { exfat_parse_directory_entry_set(DriveNumber, positions, validPositionCount); validPositionCount = 0; if (tmpEntry == EXFAT_DIRENTRY_TYPE_END) { eod = 1; } } positions[validPositionCount] = tmpPos; tmpPos += 32; validPositionCount++; } // end of looping through the current extent } // end of looping through all the extents // final parse of the last few entries in the directory exfat_parse_directory_entry_set(DriveNumber, positions, validPositionCount); return; } void exfat_parse_extents_basic(int DriveNumber, const EXFAT_CHAINED_EXTENTS &e) { // for kicks, just parse a whole bunch of entries.... local DWORD bytesPerCluster = drive[DriveNumber].bytesPerCluster; local int i = 0; local int eod = 0; local int k; local quad clusterStart; local int entriesThisExtent; local quad currentEntryStart; local ubyte currentEntryType; while ( exists(e.extent[i]) && !eod ) { // initialize to start of cluster location clusterStart = GetFilePositionForCluster(DriveNumber, e.extent[i].ClusterIndex); entriesThisExtent = e.extent[i].CountOfClusters * (bytesPerCluster / 0x20); FSeek(clusterStart); for (k = 0; k < entriesThisExtent && !eod; k++) { currentEntryStart = clusterStart + (k*0x20); currentEntryType = ReadUByte( currentEntryStart ); if (currentEntryType == EXFAT_DIRENTRY_TYPE_END) { eod = 1; } else if (currentEntryType == EXFAT_DIRENTRY_TYPE_ALLOCATION_BITMAP) { EXFAT_DIRENTRY_ALLOCATION_BITMAP allocationBitmap; } else if (currentEntryType == EXFAT_DIRENTRY_TYPE_UPCASE_TABLE) { EXFAT_DIRENTRY_UPCASE_TABLE upcaseTable; } else if (currentEntryType == EXFAT_DIRENTRY_TYPE_VOLUME_LABEL) { EXFAT_DIRENTRY_VOLUME_LABEL volumeLabel; } else if (currentEntryType == EXFAT_DIRENTRY_TYPE_FILE) { EXFAT_DIRENTRY_FILE file; } else if (currentEntryType == EXFAT_DIRENTRY_TYPE_FILE_STREAM) { EXFAT_DIRENTRY_FILE_STREAM fileStream; } else if (currentEntryType == EXFAT_DIRENTRY_TYPE_FILE_NAME) { EXFAT_DIRENTRY_FILE_NAME fileName; } else if (currentEntryType == EXFAT_DIRENTRY_TYPE_VOLUME_GUID) { EXFAT_DIRENTRY_VOLUME_GUID volumeGuid; } else if (currentEntryType == EXFAT_DIRENTRY_TYPE_TEXFAT_PADDING) { EXFAT_DIRENTRY_GENERIC padding; } else if (currentEntryType == EXFAT_DIRENTRY_TYPE_WINDOWSCE_ACL) { EXFAT_DIRENTRY_GENERIC windows_ce_acl; } else if (currentEntryType <= 0x7F) { EXFAT_DIRENTRY_GENERIC unused; } } i++; } } typedef struct (int DriveNumber, EXFAT_CLUSTER_INFO startingClusterIndex, boolean isContiguousClusters, UQUAD ValidDataLength) { local quad startPos = FTell(); local quad clusterCount = 0; local UQUAD ValidBytes = ValidDataLength; // show as local variable if (isContiguousClusters) { clusterCount += ValidDataLength / drive[DriveNumber].bytesPerCluster; if (0 != (ValidDataLength % drive[DriveNumber].bytesPerCluster)) { clusterCount++; } } if (ValidDataLength != 0) { EXFAT_CHAINED_EXTENTS extents( DriveNumber, startingClusterIndex, clusterCount ); exfat_parse_extents_as_file_data( DriveNumber, extents, ValidDataLength ); } FSeek(startPos+4); // HACK -- avoid assertion that structure end is before structure start.... } EXFAT_DATA ; // cannot specify size attribute due to bug as of v9.0.1 typedef struct (int DriveNumber, EXFAT_CLUSTER_INFO startingClusterIndex, boolean isContiguousClusters, UQUAD ValidDataLength) { local quad startPos = FTell(); local quad clusterCount = 0; local int isRoot = startof(this) == startof(drive[DriveNumber].root); // at root directory, ValidDataLength is unknown (zero) and isContiguousClusters is FALSE // at other directories, any input combination is valid if (isContiguousClusters) { Assert(ValidDataLength != 0); clusterCount += ValidDataLength / drive[DriveNumber].bytesPerCluster; if (0 != (ValidDataLength % drive[DriveNumber].bytesPerCluster)) { clusterCount++; } } if (isRoot || (clusterCount != 0)) { EXFAT_CHAINED_EXTENTS extents( DriveNumber, startingClusterIndex, clusterCount ); exfat_parse_extents_as_directory(DriveNumber, extents); } FSeek(startPos+4); // HACK -- avoid assertion that structure end is before structure start.... } EXFAT_DIRECTORY_CONTENTS ; // cannot specify size attribute due to bug as of v9.0.1 typedef struct (BYTE BytesPerSectorShift) { local int DriveNum = NumDrives++; // keep track of the index of this drive local FILE_SYSTEM_TYPE FsType = FILE_SYSTEM_TYPE_NONE; local BYTE DriveBytesPerSectorShift = BytesPerSectorShift; local DWORD bytesPerSector = BytesPerSectorShiftToBytesPerSector(BytesPerSectorShift); local int i = 0; local quad pos ; local quad fatSectorOffsetInVolume ; local quad fatByteOffsetInVolume ; local quad fatFilePosition ; local quad filePositionForFirstCluster; local quad bytesPerCluster; // Which boot region to use? First one that's valid.... local int idx = -1; EXFAT_BOOT_REGION boot_region(BytesPerSectorShift); EXFAT_BOOT_REGION boot_region(BytesPerSectorShift); for (i = 1; i >= 0; i--) { // Printf("EXFAT (%08Xh): Checking IsValidExfatBootRegion( boot_region[%d] ) ...\r\n", startof(this), i); if (IsValidExfatBootRegion(boot_region[i])) { boot_region[i].InternallyValid = 1; idx = i; } } if (-1 == idx) { Printf("EXFAT (%08Xh): Neither boot region is valid, so exiting early", startof(this)); return; } FsType = FILE_SYSTEM_TYPE_EXFAT; // active is the second FAT only when there are two AND the active index is 1 fatSectorOffsetInVolume = boot_region[idx].Boot.FirstFatSectorOffset; if ((boot_region[idx].Boot.NumberOfFatTables == 2) && (boot_region[idx].Boot.VolumeFlags.ActiveFatIndex == 1)) { fatSectorOffsetInVolume += boot_region[idx].Boot.SectorsPerFat; } fatByteOffsetInVolume = fatSectorOffsetInVolume * bytesPerSector; fatFilePosition = startof(this) + fatByteOffsetInVolume; FSeek(fatFilePosition); EXFAT_FAT_TABLE ActiveFat(boot_region[idx].Boot.CountOfDataClusters); // If there are two FATs on the media, expose the second one also. if (boot_region[idx].Boot.NumberOfFatTables == 2) { fatSectorOffsetInVolume = boot_region[idx].Boot.FirstFatSectorOffset; if (boot_region[idx].Boot.VolumeFlags.ActiveFatIndex != 1) { fatSectorOffsetInVolume += boot_region[idx].Boot.SectorsPerFat; } fatByteOffsetInVolume = fatSectorOffsetInVolume * bytesPerSector; fatFilePosition = startof(this) + fatByteOffsetInVolume; FSeek(fatFilePosition); EXFAT_FAT_TABLE BackupFat(boot_region[idx].Boot.CountOfDataClusters); } bytesPerCluster = boot_region[idx].Boot.BytesPerCluster; filePositionForFirstCluster = startof(this); filePositionForFirstCluster += boot_region[idx].Boot.DataAreaSectorOffset * bytesPerSector; FSeek( startof(boot_region[idx].Boot.RootClusterIndex) ); EXFAT_DIRECTORY_CONTENTS root( DriveNum, boot_region[idx].Boot.RootClusterIndex, false, // requires use of FAT Chain 0 // root directory has no idea how many valid bytes there could be... (defined by FAT chain) ); } EXFAT_DRIVE ; // cannot specify size attribute due to bug as of v9.0.1 //################################################################ // Combined FAT12/FAT16/FAT32 structures //################################################################ // Forward definition; struct FAT_DIRECTORY; struct FAT_BOOTSECTOR; // boot sector supporting FAT12 + FAT16 + FAT32 // Because of the different bit-widths, the enumerations and structs // related to the FAT must be separately defined for each FAT type. // Per Sweetscape support, typedefs do not work with bitfields as of v9.0c. // Therefore, every usage will require the bitfield annotation (' : 12'). typedef enum { FAT12_MEDIA_HARD_DISK = 0xff8, FAT12_MEDIA_FLOPPY_DISK = 0xff0 } FAT12_MEDIA_TYPE ; // Per Sweetscape support, typedefs do not work with bitfields as of v9.0c. // Therefore, every usage will require the bitfield annotation (' : 12'). typedef enum { FAT12_PARTITION_NOT_IN_USE = 0xfff, FAT12_PARTITION_IN_USE = 0xff7 } FAT12_PARTITION_STATE ; // Per Sweetscape support, typedefs do not work with bitfields as of v9.0c. // Therefore, every usage will require the bitfield annotation (' : 12'). typedef enum { FAT12_CLUSTER_FREE_CLUSTER = 0x000, FAT12_CLUSTER_RESERVED_0001 = 0x001, FAT12_CLUSTER_RESERVED_FFF0 = 0xFF0, FAT12_CLUSTER_RESERVED_FFF1 = 0xFF1, FAT12_CLUSTER_RESERVED_FFF2 = 0xFF2, FAT12_CLUSTER_RESERVED_FFF3 = 0xFF3, FAT12_CLUSTER_RESERVED_FFF4 = 0xFF4, FAT12_CLUSTER_RESERVED_FFF5 = 0xFF5, FAT12_CLUSTER_RESERVED_FFF6 = 0xFF6, FAT12_CLUSTER_BAD_CLUSTER = 0xFF7, FAT12_CLUSTER_END_OF_CHAIN_FFF8 = 0xFF8, FAT12_CLUSTER_END_OF_CHAIN_FFF9 = 0xFF9, FAT12_CLUSTER_END_OF_CHAIN_FFFA = 0xFFA, FAT12_CLUSTER_END_OF_CHAIN_FFFB = 0xFFB, FAT12_CLUSTER_END_OF_CHAIN_FFFC = 0xFFC, FAT12_CLUSTER_END_OF_CHAIN_FFFD = 0xFFD, FAT12_CLUSTER_END_OF_CHAIN_FFFE = 0xFFE, FAT12_CLUSTER_END_OF_CHAIN_FFFF = 0xFFF } FAT12_CLUSTER_INFO ; string ReadFAT12_CLUSTER_INFO( FAT12_CLUSTER_INFO &info ) { local string s ; if (info == FAT12_CLUSTER_FREE_CLUSTER) { s = "FREE_CLUSTER (000h)"; } else if (info == FAT12_CLUSTER_RESERVED_0001) { s = "RESERVED (001h)"; } else if (info >= 0xFF0 && info <= 0xFF6) { SPrintf(s, "RESERVED (%03xh)", info); } else if (info == FAT12_CLUSTER_BAD_CLUSTER) { s = "BAD_CLUSTER (FF7h)"; } else if (info >= 0xFF8 && info <= 0xFFF) { SPrintf(s, "END_OF_CHAIN (%03xh)", info); } else { SPrintf(s, "%d", info); // print in decimal to make easier to manually follow FAT chains... } return s; } typedef enum { FAT16_MEDIA_HARD_DISK = 0xfff8, FAT16_MEDIA_FLOPPY_DISK = 0xfff0 } FAT16_MEDIA_TYPE ; typedef enum { FAT16_PARTITION_NOT_IN_USE = 0xffff, FAT16_PARTITION_IN_USE = 0xfff7 } FAT16_PARTITION_STATE ; typedef enum { FAT16_CLUSTER_FREE_CLUSTER = 0x0000, FAT16_CLUSTER_RESERVED_0001 = 0x0001, FAT16_CLUSTER_RESERVED_FFF0 = 0xFFF0, FAT16_CLUSTER_RESERVED_FFF1 = 0xFFF1, FAT16_CLUSTER_RESERVED_FFF2 = 0xFFF2, FAT16_CLUSTER_RESERVED_FFF3 = 0xFFF3, FAT16_CLUSTER_RESERVED_FFF4 = 0xFFF4, FAT16_CLUSTER_RESERVED_FFF5 = 0xFFF5, FAT16_CLUSTER_RESERVED_FFF6 = 0xFFF6, FAT16_CLUSTER_BAD_CLUSTER = 0xFFF7, FAT16_CLUSTER_END_OF_CHAIN_FFF8 = 0xFFF8, FAT16_CLUSTER_END_OF_CHAIN_FFF9 = 0xFFF9, FAT16_CLUSTER_END_OF_CHAIN_FFFA = 0xFFFA, FAT16_CLUSTER_END_OF_CHAIN_FFFB = 0xFFFB, FAT16_CLUSTER_END_OF_CHAIN_FFFC = 0xFFFC, FAT16_CLUSTER_END_OF_CHAIN_FFFD = 0xFFFD, FAT16_CLUSTER_END_OF_CHAIN_FFFE = 0xFFFE, FAT16_CLUSTER_END_OF_CHAIN_FFFF = 0xFFFF } FAT16_CLUSTER_INFO ; string ReadFAT16_CLUSTER_INFO( FAT16_CLUSTER_INFO &info ) { local string s ; if (info == FAT16_CLUSTER_FREE_CLUSTER) { s = "FREE_CLUSTER (0000h)"; } else if (info == FAT16_CLUSTER_RESERVED_0001) { s = "RESERVED (0001h)"; } else if (info >= 0xFFF0 && info <= 0xFFF6) { SPrintf(s, "RESERVED (%04xh)", info); } else if (info == FAT16_CLUSTER_BAD_CLUSTER) { s = "BAD_CLUSTER (FFF7h)"; } else if (info >= 0xFFF8 && info <= 0xFFFF) { SPrintf(s, "END_OF_CHAIN (%04xh)", info); } else { SPrintf(s, "%d", info); // print in decimal to make easier to manually follow FAT chains... } return s; } // Per Sweetscape support, typedefs do not work with bitfields as of v9.0c. // Therefore, every usage will require the bitfield annotation (' : 28'). typedef enum { FAT32_MEDIA_HARD_DISK = 0x0ffffff8, FAT32_MEDIA_FLOPPY_DISK = 0x0ffffff0 } FAT32_MEDIA_TYPE ; // Per Sweetscape support, typedefs do not work with bitfields as of v9.0c. // Therefore, every usage will require the bitfield annotation (' : 28'). typedef enum { FAT32_PARTITION_NOT_IN_USE = 0xffffffff, FAT32_PARTITION_IN_USE = 0xfffffff7 } FAT32_PARTITION_STATE ; // Per Sweetscape support, typedefs do not work with bitfields as of v9.0c. // Therefore, every usage will require the bitfield annotation (' : 28'). typedef enum { FAT32_CLUSTER_FREE = 0x00000000, FAT32_CLUSTER_RESERVED_0001 = 0x00000001, FAT32_CLUSTER_RESERVED_FFF0 = 0x0FFFFFF0, FAT32_CLUSTER_RESERVED_FFF1 = 0x0FFFFFF1, FAT32_CLUSTER_RESERVED_FFF2 = 0x0FFFFFF2, FAT32_CLUSTER_RESERVED_FFF3 = 0x0FFFFFF3, FAT32_CLUSTER_RESERVED_FFF4 = 0x0FFFFFF4, FAT32_CLUSTER_RESERVED_FFF5 = 0x0FFFFFF5, FAT32_CLUSTER_RESERVED_FFF6 = 0x0FFFFFF6, FAT32_CLUSTER_BAD_CLUSTER = 0x0FFFFFF7, FAT32_CLUSTER_END_OF_CHAIN_FFF8 = 0x0FFFFFF8, FAT32_CLUSTER_END_OF_CHAIN_FFF9 = 0x0FFFFFF9, FAT32_CLUSTER_END_OF_CHAIN_FFFA = 0x0FFFFFFA, FAT32_CLUSTER_END_OF_CHAIN_FFFB = 0x0FFFFFFB, FAT32_CLUSTER_END_OF_CHAIN_FFFC = 0x0FFFFFFC, FAT32_CLUSTER_END_OF_CHAIN_FFFD = 0x0FFFFFFD, FAT32_CLUSTER_END_OF_CHAIN_FFFE = 0x0FFFFFFE, FAT32_CLUSTER_END_OF_CHAIN_FFFF = 0x0FFFFFFF } FAT32_CLUSTER_INFO ; string ReadFAT32_CLUSTER_INFO( FAT32_CLUSTER_INFO &info ) { // Top four bits are reserved local ulong tmp = info & 0x0FFFFFFF; local ulong reserved_bits = info & 0xF0000000; local string s = ""; if (tmp == FAT32_CLUSTER_FREE) { s = "FREE_CLUSTER (00000000h)"; } else if (tmp == 0x00000001) { s = "RESERVED (00000001h)"; } else if (tmp >= 0x0FFFFFF0 && tmp <= 0x0FFFFFF6) { SPrintf(s, "RESERVED (%08xh)", tmp); } else if (tmp == FAT32_CLUSTER_BAD_CLUSTER) { s = "BAD_CLUSTER (0FFFFFF7h)"; } else if (tmp >= 0x0FFFFFF8 && tmp <= 0x0FFFFFFF) { SPrintf(s, "END_OF_CHAIN (%08xh)", info); } else { SPrintf(s, "%d", info); // print in decimal to make easier to manually follow FAT chains... } return s; } typedef struct (DWORD CountOfDataClusters) { Assert(CountOfDataClusters <= 4085); local DWORD count_of_data_clusters = CountOfDataClusters; // only depends on parameter, so optimize is OK local DWORD i = CountOfDataClusters+2; // only depends on parameter, so optimize is OK // BUGBUG - Must currently presume caller has NOT disabled bitfield padding // Sweetscape is considering exposing at least IsBitfieldPaddingEnabled(), // which will allow safely restoring the prior bitfield padding state. // However, that functionality does not yet exist as of v9.0c. BitfieldDisablePadding(); // Per Sweetscape support, typedefs do not work with bitfields as of v9.0c. // Therefore, every usage will require the bitfield annotation (' : 12'). FAT12_MEDIA_TYPE MediaType : 12; // Per Sweetscape support, typedefs do not work with bitfields as of v9.0c. // Therefore, every usage will require the bitfield annotation (' : 12'). FAT12_PARTITION_STATE PartitionState : 12; FSkip( -3 ); // HACKHACK - Per Sweetscape support, there is also no way to create // an array of bitfields that is shown collapsed (like "real" arrays), // at least as of v9.0c. // A loop such as below has the best chance of a future version // collapsing the displayed array. while (i > 0) { // Per Sweetscape support, typedefs do not work with bitfields as of v9.0c. // Therefore, every usage will require the bitfield annotation (' : 12'). FAT12_CLUSTER_INFO Cluster : 12; i -= 1; } // BUGBUG - Must currently presume caller has NOT disabled bitfield padding // Sweetscape is considering exposing at least IsBitfieldPaddingEnabled(), // which will allow safely restoring the prior bitfield padding state. // However, that functionality does not yet exist as of v9.0c. BitfieldEnablePadding(); } FAT12_FAT_TABLE ; string ReadFAT12_FAT_TABLE( FAT12_FAT_TABLE &fat_table ) { // HACKHACK - Per Sweetscape support, there is also no way to create // an array of bitfields that is shown collapsed (like "real" arrays), // at least as of v9.0c. // The entire purpose of this routine is to make the UI as similar // as possible to an array. local string s ; SPrintf(s, "enum FAT12_CLUSTER_INFO Cluster[%d]", fat_table.count_of_data_clusters); return s; } typedef struct (quad CountOfDataClusters) { // show the first two entries as media type and partition status // FAT16 only supports 512 byte sectors(?) FAT16_MEDIA_TYPE MediaType; // legacy: FAT index 0 was used for media type FAT16_PARTITION_STATE PartitionState; // legacy: FAT index 1 was used for partition state // Then seek back, so FAT array indices correspond to stored values in the FAT chain FSkip( -4 ); FAT16_CLUSTER_INFO Cluster[ CountOfDataClusters + 2]; } FAT16_FAT_TABLE; typedef struct (DWORD CountOfDataClusters) { local DWORD count_of_data_clusters = CountOfDataClusters; // only depends on parameter, so optimize is OK local DWORD i = CountOfDataClusters+2; // only depends on parameter, so optimize is OK // Per Sweetscape support, typedefs do not work with bitfields as of v9.0c. // Therefore, every usage will require the bitfield annotation (' : 28'). FAT32_MEDIA_TYPE MediaType : 28; // Per Sweetscape support, typedefs do not work with bitfields as of v9.0c. // Therefore, every usage will require the bitfield annotation (' : 28'). FAT32_PARTITION_STATE PartitionState : 28; // HACKHACK - Per Sweetscape support, there is also no way to create // an array of bitfields that is shown collapsed (like "real" arrays), // at least as of v9.0c. // A loop such as below has the best chance of a future version // collapsing the displayed array. FSkip( -8 ); while (i > 0) { // Per Sweetscape support, typedefs do not work with bitfields as of v9.0c. // Therefore, every usage will require the bitfield annotation (' : 28'). FAT32_CLUSTER_INFO Cluster : 28; i -= 1; } } FAT32_FAT_TABLE ; string ReadFAT32_FAT_TABLE( FAT32_FAT_TABLE &fat_table ) { // HACKHACK - Per Sweetscape support, there is also no way to create // an array of bitfields that is shown collapsed (like "real" arrays), // at least as of v9.0c. // The entire purpose of this routine is to make the UI as similar // as possible to an array. local string s ; SPrintf(s, "enum FAT32_CLUSTER_INFO Cluster[%d]", fat_table.count_of_data_clusters); return s; } typedef struct { local INT64 tmp ; local DWORD ClusterSize; local DWORD NumberOfSectors; local DWORD SectorsPerFat; local DWORD CountOfDataClusters; local DWORD MaximumValidClusterNumber; local FILE_SYSTEM_TYPE FsType = FILE_SYSTEM_TYPE_NONE; BYTE jmp[3] ; CHAR OemName[8]; USHORT BytesPerSector ; // legal == { 512, 1024, 2048, 4096 } UBYTE SectorsPerCluster ; // legal == { 1, 2, 4, 8, 16, 32, 64, 128 } USHORT ReservedSectors ; // must not be zero; legal for FAT12/16 == { 1 }, typically 32 for FAT32 UBYTE NumberOfFatTables ; // must not be zero; warn if this is not set to the value 1 or 2 USHORT MaxRootDirEntries ; // legal for FAT12/16 == N * (BytesPerSector / 32), N is non-zero; must be {0} for FAT32 USHORT NumberOfSectors16 ; // must be {0} for FAT32; if {0}, then NumberOfSectors32 must be non-zero MEDIA MediaDescriptor ; // legacy USHORT SectorsPerFat16 ; // must be {0} for FAT32; must be non-zero for FAT12/16 USHORT SectorsPerTrack ; // legacy USHORT HeadsPerCylinder ; // legacy ULONG NumHiddenSectors ; // legacy ULONG NumberOfSectors32 ; // must be non-zero for FAT32; must be >= 0x10000 if NumberOfSectors16 is zero // PREVENTS ARRAY OPTIMIZATION because depends on instance value if (0 == this.SectorsPerFat16) { // FAT32 starting at sector offset 36 DWORD SectorsPerFat32 ; WORD ActiveFatIndex : 4 ; // zero-based index of the active FAT WORD FlagsReserved1 : 3 ; WORD TransactionFat : 1 ; // 1 means only one FAT active, as indicated by ActiveFatIndex; 0 means both FATs are mirrored WORD FlagsReserved2 : 8 ; WORD Version ; // must be 0 DWORD RootCluster ; // cluster number (FAT index) for root. usually 2. preferably first non-bad sector. WORD InfoSector ; // usually 1. WORD BootBackupStart ; // usually 6. No other value than 6 is recommended BYTE Reserved[12] ; // set to zero by formatting utility, no indicaton of other uses but shall be ignored/preserved } BYTE DriveNumber ; BYTE Unused ; BYTE ExtBootSignature ; DWORD SerialNumber ; // only valid if ExtBootSignature == 0x29 CHAR VolumeLabel[11] ; // only valid if ExtBootSignature == 0x29 CHAR FileSystemLabel[8] ; // only valid if ExtBootSignature == 0x29 // PREVENTS ARRAY OPTIMIZATION tmp = 510 - (FTell() - startof(jmp)); // 420 for FAT32, 448 for FAT16/12 UBYTE BootCode[tmp] ; WORD EndOfSectorMarker ; // Helpers to make FAT12/16/32 more common -- all of which prevent array optimization ClusterSize = this.BytesPerSector * this.SectorsPerCluster; NumberOfSectors = (this.NumberOfSectors16 != 0) ? this.NumberOfSectors16 : this.NumberOfSectors32; SectorsPerFat = (this.SectorsPerFat16 != 0) ? this.SectorsPerFat16 : this.SectorsPerFat32; CountOfDataClusters = ReadFAT_BOOTSECTOR_CountOfDataClusters(this); MaximumValidClusterNumber = CountOfDataClusters + 1; // +2, but zero-based indexing, so largest valid value is Count+1 if (CountOfDataClusters == 0) { //Printf("WARNING: Count of data clusters computed as zero, so not valid FAT filesystem\r\n"); FsType = FILE_SYSTEM_TYPE_NONE; // oops } else if (CountOfDataClusters < 4085) { FsType = FILE_SYSTEM_TYPE_FAT12; } else if (CountOfDataClusters < 65525) { FsType = FILE_SYSTEM_TYPE_FAT16; } else if (CountOfDataClusters < 0x0FFFFFF0 - 2) { FsType = FILE_SYSTEM_TYPE_FAT32; } else { FsType = FILE_SYSTEM_TYPE_NONE; // oops } } FAT_BOOTSECTOR ; string ReadFAT_BOOTSECTOR( FAT_BOOTSECTOR &boot ) { return boot.VolumeLabel; } DWORD ReadFAT_BOOTSECTOR_SectorsRequiredForDataClusters( FAT_BOOTSECTOR &boot ) { // First determine the number of bytes required. local DWORD required_bytes ; local DWORD required_entries ; local DWORD required_sectors_per_fat ; // FAT tracks two extra entries at indices 0 and 1... required_entries = boot.CountOfDataClusters + 2; if (boot.FsType == FILE_SYSTEM_TYPE_FAT32) { // FAT32 never has sector-spanning entries // FAT32 is four bytes per entry required_bytes = 4 * required_entries; } else if (boot.FsType == FILE_SYSTEM_TYPE_FAT16) { // FAT16 never has sector-spanning entries // FAT16 is two bytes per entry required_bytes = 2 * required_entries; } else if (boot.FsType == FILE_SYSTEM_TYPE_FAT12) { // FAT12 has sector-spanning entries // FAT12 has twelve BITS per entry // ... or two entries == three bytes required_bytes = (required_entries / 2) * 3; if (0 != (required_entries % 2)) { required_bytes += 2; } } else { Printf("INVALID FAT (%08Xh): Unsupported file system type when calling IsFatTableSizeSufficient()\r\n", startof(boot)); return false; } // convert the number of bytes to number of sectors (rounding up) required_sectors_per_fat = required_bytes / boot.BytesPerSector; if (0 != (required_bytes % boot.BytesPerSector)) { required_sectors_per_fat += 1; // round up } return required_sectors_per_fat; } DWORD ReadFAT_BOOTSECTOR_CountOfDataClusters( FAT_BOOTSECTOR &boot ) { local DWORD rootDirectoryByteCount ; local DWORD rootDirectorySectors ; local DWORD sectorsPerFat ; local DWORD totalSectors ; local DWORD dataSectors ; local DWORD countOfClusters ; if (boot.BytesPerSector == 0) return 0; if (!IsExactlyOneBitSet(boot.BytesPerSector)) return false; if (boot.BytesPerSector != 512) { // TODO: Fix 4k native sectors to show correct file data Printf("WARNING: Potentially valid FAT file system at 0x%x.\r\n", startof(boot)); Printf(" Shows non-512 bytes per sector (0x%x)\r\n", boot.BytesPerSector); Printf(" Treating as unsupported drive due to known bugs\r\n"); return 0; } // First, the number of sectors in the special root directory area rootDirectoryByteCount = boot.MaxRootDirEntries * 32; rootDirectorySectors = rootDirectoryByteCount / boot.BytesPerSector; if (rootDirectoryByteCount % boot.BytesPerSector != 0) { rootDirectorySectors++; } // Next, the number of sectors in the data region sectorsPerFat = boot.SectorsPerFat; totalSectors = boot.NumberOfSectors; dataSectors = totalSectors; dataSectors -= boot.ReservedSectors; dataSectors -= boot.NumberOfFatTables * sectorsPerFat; dataSectors -= rootDirectorySectors; countOfClusters = dataSectors / boot.SectorsPerCluster; // note that this rounds down return countOfClusters; } boolean IsValidFatBootSector(FAT_BOOTSECTOR &boot) { local DWORD required_sectors_per_fat ; // FAT12/16/32 don't specify quite as much as mandatory... if (boot.EndOfSectorMarker != 0xAA55) { Printf("INVALID FAT (%08Xh): EndOfSectorMark not 0xAA55 (0x55 0xAA)\r\n", startof(boot)); return false; } if (boot.FsType == FILE_SYSTEM_TYPE_NONE) { return false; } if ((boot.FsType != FILE_SYSTEM_TYPE_FAT32) && (boot.FsType != FILE_SYSTEM_TYPE_FAT16) && (boot.FsType != FILE_SYSTEM_TYPE_FAT12) ) { Printf("INVALID FAT (%08Xh): internal FsType not set to FAT32/16/12 (%s)\r\n", startof(boot), ReadFILE_SYSTEM_TYPE(boot.FsType)); return false; } if ((boot.BytesPerSector != 512) && (boot.BytesPerSector != 1024) && (boot.BytesPerSector != 2048) && (boot.BytesPerSector != 4096)) { Printf("INVALID FAT (%08Xh): BytesPerSector (%x)\r\n", startof(boot), boot.BytesPerSector); return false; } if ((boot.SectorsPerCluster != 128) && (boot.SectorsPerCluster != 64) && (boot.SectorsPerCluster != 32) && (boot.SectorsPerCluster != 16) && (boot.SectorsPerCluster != 8) && (boot.SectorsPerCluster != 4) && (boot.SectorsPerCluster != 2) && (boot.SectorsPerCluster != 1)) { Printf("INVALID FAT (%08Xh): SectorsPerCluster (%x)\r\n", startof(boot), boot.SectorsPerCluster); return false; } if (boot.ReservedSectors == 0) { Printf("INVALID FAT (%08Xh): ReservedSectors is zero\r\n", startof(boot)); return false; } if ((boot.NumberOfFatTables != 1) && (boot.NumberOfFatTables != 2)) { Printf("INVALID FAT (%08Xh): NumberOfFatTables invalid (%x)\r\n", startof(boot), boot.NumberOfFatTables); return false; } if (boot.FsType == FILE_SYSTEM_TYPE_FAT32) { if (boot.MaxRootDirEntries != 0) { Printf("INVALID FAT32 (%08Xh): MaxRootDirEntries must be zero (%x)\r\n", startof(boot), boot.MaxRootDirEntries); return false; } if (boot.NumberOfSectors16 != 0) { Printf("INVALID FAT32 (%08Xh): NumberOfSectors16 must be zero (%x)\r\n", startof(boot), boot.NumberOfSectors16); return false; } if (boot.NumberOfSectors32 == 0) { Printf("INVALID FAT32 (%08Xh): NumberOfSectors32 cannot be zero\r\n", startof(boot)); return false; } if (boot.SectorsPerFat16 != 0) { Printf("INVALID FAT32 (%08Xh): SectorsPerFat16 must be zero (%x)\r\n", startof(boot), boot.SectorsPerFat16); return false; } if (boot.SectorsPerFat32 == 0) { Printf("INVALID FAT32 (%08Xh): SectorsPerFat32 cannot be zero\r\n", startof(boot)); return false; } if (boot.RootCluster == 0) { Printf("INVALID FAT32 (%08Xh): RootCluster cannot be zero\r\n", startof(boot)); return false; } } else if ((boot.FsType == FILE_SYSTEM_TYPE_FAT16) || (boot.FsType == FILE_SYSTEM_TYPE_FAT12)) { if (boot.MaxRootDirEntries == 0) { Printf("INVALID FAT12/16 (%08Xh): MaxRootDirEntries cannot be zero\r\n", startof(boot)); return false; } if ((boot.NumberOfSectors16 == 0) && (boot.NumberOfSectors32 == 0)) { Printf("INVALID FAT12/16 (%08Xh): NumberOfSectors16 and NumberOfSectors32 cannot both be zero\r\n", startof(boot)); return false; } if (boot.SectorsPerFat16 == 0) { Printf("INVALID FAT12/16 (%08Xh): SectorsPerFat16 cannot be zero (%x)\r\n", startof(boot)); return false; } } required_sectors_per_fat = ReadFAT_BOOTSECTOR_SectorsRequiredForDataClusters(boot); if (boot.SectorsPerFat < required_sectors_per_fat) { Printf("INVALID FAT (%08Xh): 0x%x data clusters requires 0x%x sectors for each FAT, but boot indicates only 0x%x sectors per FAT.\r\n", startof(boot), boot.CountOfDataClusters, required_sectors_per_fat, boot.SectorsPerFAT); return false; } return true; } // Define a FAT 12/16/32 combined drive type typedef struct { local int DriveNum = NumDrives++; // keep track of the index of this drive local FILE_SYSTEM_TYPE FsType = FILE_SYSTEM_TYPE_NONE; // local BYTE DriveBytesPerSectorShift = BytesPerSector; // BUGBUG -- check for 4k support in FAT, then add paramter to struct to allow creation... local quad FirstFatTableFilePos; local quad DataAreaFilePos; local DWORD ClusterSize; local quad FatTableSector; local quad FatTableAreaSizeInSectors; local quad RootDirEntrySector; local quad RootDirEntryFilePos; local quad DataAreaSector; local quad VolumeStartPosition=FTell(); local DWORD MaxValidCluster; local int i ; local quad BytesPerSector; local quad CurrentPosSector; local quad pos ; // FAT Boot sector FAT_BOOTSECTOR boot_sector ; if (!IsValidFatBootSector(boot_sector)) { Printf("INVALID FAT (%08Xh): Drive creation attempted when boot sector invalid?", startof(boot_sector)); return; } BytesPerSector = boot_sector.BytesPerSector; CurrentPosSector = VolumeStartPosition / BytesPerSector; // Calculate offsets and sizes for file system areas FatTableSector = CurrentPosSector + boot_sector.ReservedSectors; // e.g., used for alignment on flash media RootDirEntrySector = FatTableSector + (boot_sector.SectorsPerFat * boot_sector.NumberOfFatTables); RootDirEntryFilePos = RootDirEntrySector * BytesPerSector; FirstFatTableFilePos = FatTableSector * BytesPerSector; FatTableAreaSizeInSectors = RootDirEntrySector - FatTableSector; DataAreaSector = RootDirEntrySector + ((boot_sector.MaxRootDirEntries * 32) / BytesPerSector); DataAreaFilePos = DataAreaSector * BytesPerSector; ClusterSize = BytesPerSector * boot_sector.SectorsPerCluster; MaxValidCluster = boot_sector.MaximumValidClusterNumber; // Add this to the Drive structure also FsType = boot_sector.FsType; // Add the FAT structures for (i = 0; i < boot_sector.NumberOfFatTables; i++) { pos = FirstFatTableFilePos; pos += (boot_sector.SectorsPerFat * boot_sector.BytesPerSector * i); FSeek(pos); if (boot_sector.FsType == FILE_SYSTEM_TYPE_FAT32) { FAT32_FAT_TABLE table(boot_sector.CountOfDataClusters); } else if (boot_sector.FsType == FILE_SYSTEM_TYPE_FAT16) { FAT16_FAT_TABLE table(boot_sector.CountOfDataClusters); } else if (boot_sector.FsType == FILE_SYSTEM_TYPE_FAT12) { FAT12_FAT_TABLE table(boot_sector.CountOfDataClusters); } } // FAT Directory Entry FSeek( RootDirEntryFilePos ); FAT_DIRECTORY root_dir; } FAT_DRIVE ; //################################################################ // FAT Directory (Shared between FAT12, FAT16 and FAT32) //################################################################ // FAT Directory Attribute // Per Sweetscape support, typedefs do not work with bitfields as of v9.0c. // Therefore, every usage will require the bitfield annotation (' : 6'). typedef enum { FAT_ATTR_LongFileNameEntry = 0x0F, // special case // bits 6 & 7 are reserved FAT_ATTR_NoneOrFile = 0x00, FAT_ATTR_ReadOnly = 0x01, // bit0 FAT_ATTR_Hidden = 0x02, // bit1 FAT_ATTR_System = 0x04, // bit2 FAT_ATTR_VolumeId = 0x08, // bit3 FAT_ATTR_Directory = 0x10, // bit4 FAT_ATTR_Archive = 0x20, // bit5 // Common valid combinations of above FAT_ATTR_ReadOnly_Hidden = 0x03, FAT_ATTR_ReadOnly_System = 0x05, FAT_ATTR_Hidden_System = 0x06, FAT_ATTR_ReadOnly_Hidden_System = 0x07, // VolumeID is set only alone, or in FAT_ATTR_LongFileNameEntry // as above, + _Directory FAT_ATTR_ReadOnly_Hidden_Directory = 0x13, FAT_ATTR_ReadOnly_System_Directory = 0x15, FAT_ATTR_Hidden_System_Directory = 0x16, FAT_ATTR_ReadOnly_Hidden_System_Directory = 0x17, // as above, + _Archive FAT_ATTR_ReadOnly_Hidden_Archive = 0x23, FAT_ATTR_ReadOnly_System_Archive = 0x25, FAT_ATTR_Hidden_System_Archive = 0x26, FAT_ATTR_ReadOnly_Hidden_System_Archive = 0x27, FAT_ATTR_ReadOnly_Hidden_Directory_Archive = 0x33, FAT_ATTR_ReadOnly_System_Directory_Archive = 0x35, FAT_ATTR_Hidden_System_Directory_Archive = 0x36, FAT_ATTR_ReadOnly_Hidden_System_Directory_Archive = 0x37, } FAT_ATTR_TYPE ; // NOTE: used in routine that reads-ahead to detects if the entry if LFN or not local const uchar FAT_ATTR_TYPE_RESERVED_MASK = 0xC0; local const uchar FAT_ATTR_TYPE_VALID_MASK = 0x3F; string ReadFAT_ATTR_TYPE(FAT_ATTR_TYPE &type) { local uchar tmp = type & FAT_ATTR_TYPE_VALID_MASK; // top two bits are reserved local string s ; if (tmp == FAT_ATTR_LongFileNameEntry) { s += "LongFileNameEntry"; } else { local uchar bitsFound = 0; if (tmp & FAT_ATTR_ReadOnly) { if ( bitsFound) { s += "_"; } s += "ReadOnly"; bitsFound++; } if (tmp & FAT_ATTR_Hidden) { if ( bitsFound) { s += "_"; } s += "Hidden"; bitsFound++; } if (tmp & FAT_ATTR_System) { if ( bitsFound) { s += "_"; } s += "System"; bitsFound++; } if (tmp & FAT_ATTR_VolumeId) { if ( bitsFound) { s += "_"; } s += "VolumeId"; bitsFound++; } if (tmp & FAT_ATTR_Directory) { if ( bitsFound) { s += "_"; } s += "Directory"; bitsFound++; } if (tmp & FAT_ATTR_Archive) { if ( bitsFound) { s += "_"; } s += "Archive"; bitsFound++; } if (!bitsFound) { s += "NoneOrFile"; } } local uchar tmp2 = type & FAT_ATTR_TYPE_RESERVED_MASK; // parser does not allow type to be only six bits long.... if (tmp2) { local string q ; SPrintf(q, " + (Reserved bits) %02xh", tmp2); s += q; } return s; } // FAT Short directory entry typedef struct // Normal-Short structure { CHAR Name[8]; // Blank-padded name CHAR Extension[3]; //Blank-padded extension FAT_ATTR_TYPE Attribute : 6; // only low six bits are valid UBYTE Reserved ; UBYTE CreateTime10ms; //10-ms units "Create Time" refinement DOSTIME CreateTime; DOSDATE CreateDate; DOSDATE AccessDate; USHORT HighCluster ; // Used on FAT32 only DOSTIME UpdateTime; DOSDATE UpdateDate; USHORT Cluster ; // first cluster *NUMBER* ULONG FileSizeInBytes ; // file size in bytes (always zero for directories). } FAT_SHORTENTRY ; string ReadFAT_SHORTENTRY_Filename( FAT_SHORTENTRY &f ) { local int nameLength = 8; local int extensionLength = 3; local string name ; local string extension ; local int addPeriod ; local string result ; if (f.Name[0]==0) { return ""; // indicator of last directory entry } while (nameLength > 0 && f.Name [ nameLength - 1 ] == ' ') { nameLength -= 1; } while (extensionLength > 0 && f.Extension[ extensionLength - 1 ] == ' ') { extensionLength -= 1; } if (nameLength > 0) { Strncpy(name, f.Name, nameLength ); } if (extensionLength > 0) { Strncpy(extension, f.Extension, extensionLength); } // Add period only if extension exists, unless it's a FAT_ATTR_LongFileNameEntry or FAT_ATTR_VolumeID addPeriod = extensionLength > 0; if (f.Attribute == FAT_ATTR_LongFileNameEntry) { addPeriod = false; } else if (f.Attribute & FAT_ATTR_VolumeId) { addPeriod = false; } result = name; if (addPeriod) { result += "."; } result += extension; if((uchar)result[0] == 0xE5 ) { result = "?" + SubStr(result, 1); } else if ((uchar)result[0] == 0x05) { // 0xE5 is a valid lead byte in Kanji. // for this case, specification stores it as value 0x05. result = "\xE5" + SubStr(result, 1); } return result; } string ReadFAT_SHORTENTRY( FAT_SHORTENTRY &f ) { local string s ; if(f.Name[0]==0) { return "Last Dir Entry (Empty)"; } s = ReadFAT_SHORTENTRY_Filename(f); if( (uchar)f.Name[0] == 0xE5 ) // must use structure array, not the processed filename, due to 0x05 --> 0xE5 Kanji conversion { return "**Erased name '?" + StrDel(s, 0, 1) + "' (" + ReadFAT_ATTR_TYPE(f.Attribute) + ")"; } else { return s + " (" + ReadFAT_ATTR_TYPE(f.Attribute) + ")"; } } typedef struct { UBYTE LFN_RecSeqNum : 6; // bit0-5 LFN sequence number (1..63) UBYTE Last_LFN_record : 1; // bit6 Last LFN record in the sequence UBYTE LFN_Erased : 1; // bit7 LFN record is an erased long name entry or maybe if it is part of an erased long name? } tLFN_RecordSeqNum; // FAT Long directory entry typedef struct { typedef union ulfn { tLFN_RecordSeqNum LFN_RecordSeqNum; // LFN Record Sequence Number unsigned char char0 ; } ULFN; ULFN LFN; wchar_t UnicodeChar1[5]; // 5 UNICODE characters, LFN first part. FAT_ATTR_TYPE Attribute : 6; // This field contains the special value of 0Fh, which indicates an LFN entry. UBYTE Reserved ; UBYTE ChkShortName ; // Checksum of short name entry, used to validate that the LFN entry belongs to the short name entry following. According to Ralf Brown's interrupt list, the checksum is computed by adding up all the short name characters and rotating the intermediate value right by one bit position before adding each character. wchar_t UnicodeChar2[6]; // 6 UNICODE characters, LFN 2nd part. USHORT Cluster ; // Initial cluster number, which is always zero for LFN entries. wchar_t UnicodeChar3[2]; // 2 UNICODE characters, LFN 3rd part. } FAT_LONGENTRY; struct FAT_FILE_DATA; // Forward definition // Move the read position to the start of the given cluster void FAT_JumpToCluster( DWORD cluster, int DriveNum ) { FSeek( drive[DriveNum].DataAreaFilePos + (cluster-2)*(quad)drive[DriveNum].ClusterSize ); } // Return the cluster the address belongs to DWORD FAT_AddressToCluster( UQUAD address, int DriveNum ) { return (DWORD)((address - drive[DriveNum].DataAreaFilePos) / drive[DriveNum].ClusterSize) + 2; } // // NOTE: *** THIS IS A HACK. *** Looking at the code, it would // appear that there is a major bug where a subdirectory // would overwrite these variables, and then the parent // directory would end up in an entirely wrong location. // // So... what's the hack? // // FAT_DIRECTORY is an "On-Demand" structure, which isn't // actually parsed until the user expands the node. // Therefore, the variables here are only used within a // single FAT_DIRECTORY context at any time. // Nice hack. Just wish it had been documented. // // Special local variables used when defining FAT dir entries. // // These XYZZY_* variables are valid only within the context of // a SINGLE FAT_DIRECTORY creation, which typically spans multiple // FAT_DIRECTORY_ENTRY_SETs, each of which may include many // directory entries of 0x20 bytes each. These variables hold // state used to handle cluster-spanning entries, etc. // local UQUAD XYZZY_Start ; local UQUAD XYZZY_End ; // end of contiguous area local WORD XYZZY_IsFirstCluster ; // Check for directory reads that go off the end of a cluster - // need to jump to the next cluster if so int FAT_CheckDirEntryRead( int DriveNum ) { // Record positions if( XYZZY_IsFirstCluster ) XYZZY_End = FTell(); XYZZY_Start = FTell(); if (XYZZY_Start == drive[DriveNum].DataAreaFilePos) { // HACK for FAT16 root directory ... when it reaches DataAreaFilePos, treat as end-of-cluster return 1; } // the second condition of this IF() statement was added to allow FAT16 root directory // to be processed, because the FAT16 root directory is contiguous and outside the // normal set of clusters (and thus has a hard-coded maximum entry count at format time). if( ((XYZZY_Start - drive[DriveNum].DataAreaFilePos) % drive[DriveNum].ClusterSize == 0) && (XYZZY_Start > drive[DriveNum].DataAreaFilePos) ) // HACK - FAT16 root is contiguous and not in FAT table { XYZZY_IsFirstCluster = false; // really means more than one cluster.... // Hit the end of the cluster // Must jump to the next cluster .... local DWORD Cluster = FAT_AddressToCluster( XYZZY_Start, DriveNum ); // current position is actually in next cluster, // so use prior cluster number Cluster -= 1; // get the next cluster from the FAT table Cluster = drive[DriveNum].table.Cluster[ Cluster ]; // exit with status 0 if this is the end of the FAT chain if( (Cluster < 2) || (Cluster > drive[DriveNum].MaxValidCluster)) { return 1; //signal to caller this is the end of the record } // else jump to the next cluster and continue FAT_JumpToCluster( Cluster, DriveNum ); XYZZY_Start = FTell(); } return 0; } FAT_ATTR_TYPE FAT_DirectoryEntryType( int64 directoryEntryStart ) { return ReadByte( directoryEntryStart + 11 ) & FAT_ATTR_TYPE_VALID_MASK; } int FAT_IsDirectoryEntryLFN( int64 directoryEntryStart ) { // per Microsoft specification, the correct check is: // (((LDIR_attr & ATTR_LONG_NAME_MASK) == ATTR_LONG_NAME) && (LDIR_Ord != 0xE5)) local byte firstByte = ReadByte( directoryEntryStart ); local byte type = FAT_DirectoryEntryType(directoryEntryStart); return (type == FAT_ATTR_LongFileNameEntry) && (firstByte != 0xE5); } int FAT_IsDirectoryEntryVolumeID( int64 directoryEntryStart ) { local byte type = FAT_DirectoryEntryType(directoryEntryStart); return ( !FAT_IsDirectoryEntryLFN(directoryEntryStart) && ((type & FAT_ATTR_VolumeId) != 0) ); } // FAT Directory Entry Set typedef struct { // Copy offset info from parent local int DriveNum = parentof(this).DriveNum; // keep track of which drive this belongs to local int i ; local DWORD Cluster ; local uchar is_orphan = 0; local uchar is_complete = 0; local uchar end_of_fat_chain = 0; // Read Long/Short directory entries XYZZY_End = FTell(); XYZZY_IsFirstCluster = true; local int tmp = FAT_IsDirectoryEntryLFN(FTell()); if( FAT_IsDirectoryEntryLFN(FTell()) ) // LFN Entry { local unsigned char NumberOfLFNEntry ; local unsigned char dirname0 ; dirname0 = ReadByte(FTell()); if( !(dirname0 == 0x00) ) // 0x00 indicates no more valid directory entries follow this entry { if( dirname0==0xE5 ) // Empty/Erased { for(i=0;i<63;i++) // add LFN entries to the directory entry set { dirname0=ReadByte(FTell()); if( !(dirname0==0xE5) ) // Check still Empty/Erased ? break; if( !FAT_IsDirectoryEntryLFN(FTell()) ) // Check is still LFN ? break; // Every time add a directory entry, also must check and adjust // file position for cluster boundary, following FAT chain FAT_LONGENTRY long_entry; end_of_fat_chain = FAT_CheckDirEntryRead( DriveNum ); if (end_of_fat_chain) { is_orphan |= 0x01; } } } else { // Every time add a directory entry, also must check and adjust // file position for cluster boundary, following FAT chain FAT_LONGENTRY long_entry; end_of_fat_chain = FAT_CheckDirEntryRead( DriveNum ); NumberOfLFNEntry = long_entry.LFN.LFN_RecordSeqNum.LFN_RecSeqNum; local unsigned char expectedLfnEntry = NumberOfLFNEntry; for( i = 1; (!end_of_fat_chain) && (i < NumberOfLFNEntry); i++) { expectedLfnEntry -= 1; // Have to read LFN entry here to verify number! if ( !FAT_IsDirectoryEntryLFN(FTell()) ) { is_orphan |= 0x10; break; } if ( ReadByte() != expectedLfnEntry ) { is_orphan |= 0x20; break; } // Every time add a directory entry, also must check and adjust // file position for cluster boundary, following FAT chain FAT_LONGENTRY long_entry; end_of_fat_chain = FAT_CheckDirEntryRead( DriveNum ); if (end_of_fat_chain) { is_orphan |= 0x02; } // if the LFN entry number does not match, mark this directory entry set as "ORPHANED" if ( long_entry[i].LFN.LFN_RecordSeqNum.LFN_RecSeqNum != expectedLfnEntry ) { is_orphan |= 0x40; break; } } } } // Long entries should be followed by a short entry with file info if ( FAT_IsDirectoryEntryLFN(FTell()) ) { is_orphan |= 0x01; } // else if (!validate_LFN_CRC()) // { // TODO: compute checksum from next directory entry, ensure it matches LFN entries // if they do not match, the LFN entries are orphans.... // is_orphan |= 0x80; // } if (!is_orphan) // only attach the short directory entry if the LFN entries are not orphaned. { // Every time add a directory entry, also must check and adjust // file position for cluster boundary, following FAT chain FAT_SHORTENTRY short_entry; end_of_fat_chain = FAT_CheckDirEntryRead( DriveNum ); } } else // first entry is not LFN entry { // Every time add a directory entry, also must check and adjust // file position for cluster boundary, following FAT chain FAT_SHORTENTRY short_entry; end_of_fat_chain = FAT_CheckDirEntryRead( DriveNum ); } // Check for file data or subdirectory if( exists(short_entry) ) { // assert( !is_orphan ); Cluster = FAT_CalculateCluster( short_entry, DriveNum ); if( Cluster >= 2 ) { // Define data for this file FAT_JumpToCluster( Cluster, DriveNum ); if( (uchar)short_entry.Name[0] == 0xE5) { FAT_FILE_DATA possibleDeletedData; // try to show the deleted information - may not be accurate } else { FAT_FILE_DATA data; } // Define the sub-directory if( (short_entry.Attribute & FAT_ATTR_Directory) && (short_entry.Name != ". ") && // bytes from structure, not formatted to remove spaces (short_entry.Name != ".. ") && // bytes from structure, not formatted to remove spaces ((uchar)short_entry.Name[0] != 0xE5) ) { // Define a sub-directory FAT_JumpToCluster( Cluster, DriveNum ); FAT_DIRECTORY sub_dir; } } } local int64 myEnd = XYZZY_End; // TODO: Determine if there is a bug here, related to the global variable XYZZY_End. // This seems likely, when two nested directories each take >1 cluster (e.g., txFAT). // e.g., create \Level1\Level2\Level3\, each with files before and after the subdirectory. FSeek( XYZZY_End ); } FAT_DIRECTORY_ENTRY_SET ; // Show file name beside directory entry int ReadFAT_SHORTENTRY_IsDeleted( FAT_SHORTENTRY &f ) { return (uchar)f.Name[0] == 0xE5; } int ReadFAT_DIRECTORY_ENTRY_SET_IsDeleted( FAT_DIRECTORY_ENTRY_SET &f ) { if( exists( f.long_entry ) ) { return ( f.long_entry[0].LFN.LFN_RecordSeqNum.LFN_Erased == 1 ); } else if ( exists( f.short_entry ) ) { return ReadFAT_SHORTENTRY_IsDeleted( f.short_entry ); } else { return 0; } } int ReadFAT_DIRECTORY_ENTRY_SET_IsDirectory( FAT_DIRECTORY_ENTRY_SET &f ) { if ( exists( f.short_entry ) ) { return (f.short_entry.Attribute & FAT_ATTR_Directory) && ((UBYTE)f.short_entry.Name[0] != 0xE5); } else { return 0; } } wstring ReadFAT_DIRECTORY_ENTRY_SET_LongFilename( FAT_DIRECTORY_ENTRY_SET &f ) { if ( !exists( f.long_entry ) ) return ""; // use number from LFN sequence number, or count of erased entries local unsigned short NumberOfLFNEntries ; local int i ; if( f.long_entry[0].LFN.LFN_RecordSeqNum.LFN_Erased == 1 ) { for ( i = 0; i < 63; i++ ) { if ( !exists(f.long_entry[i].LFN.char0) || (f.long_entry[i].LFN.char0 != 0xE5) ) { break; } } NumberOfLFNEntries = i; } else if ( f.is_orphan ) { NumberOfLFNEntries = 0; // f.long_entry[i].length; // TODO: handle orphan entries more naturally } else { NumberOfLFNEntries = f.long_entry[0].LFN.LFN_RecordSeqNum.LFN_RecSeqNum; } // start at the last of the LFN entries, work the way backwards. local wstring filename ; for( i = NumberOfLFNEntries - 1; i >= 0; i-- ) { filename += f.long_entry[i].UnicodeChar1; // TODO: Correct name to reflect this is a wstr, not wchar filename += f.long_entry[i].UnicodeChar2; // TODO: Correct name to reflect this is a wstr, not wchar filename += f.long_entry[i].UnicodeChar3; // TODO: Correct name to reflect this is a wstr, not wchar } filename += f.long_entry[0].UnicodeChar1; // TODO: Correct name to reflect this is a wstr, not wchar filename += f.long_entry[0].UnicodeChar2; // TODO: Correct name to reflect this is a wstr, not wchar filename += f.long_entry[0].UnicodeChar3; // TODO: Correct name to reflect this is a wstr, not wchar // Finally, if the resulting filename has any 0xFFFF characters, trim everything past that point. local int endPos = WStrchr( filename , 0xFFFF ); if (endPos != -1) { filename = WSubStr( filename , 0, endPos ); } return filename; } wstring ReadFAT_DIRECTORY_ENTRY_SET( FAT_DIRECTORY_ENTRY_SET &f ) { local unsigned short i ; local unsigned short NumberOfLFNEntry ; local wstring filename = ""; local wstring attributeInfo = ""; if ( exists( f.long_entry ) ) { filename = ReadFAT_DIRECTORY_ENTRY_SET_LongFilename(f); } else if ( exists (f.short_entry) ) { filename = ReadFAT_SHORTENTRY_Filename(f.short_entry); } else { return ""; } // Add attribute info if ( exists(f.short_entry) ) { attributeInfo = " (" + ReadFAT_ATTR_TYPE(f.short_entry.Attribute) + ")"; } if ( ReadFAT_DIRECTORY_ENTRY_SET_IsDirectory(f) ) { filename = "/" + filename; } if (ReadFAT_DIRECTORY_ENTRY_SET_IsDeleted(f)) { return "**Erased name '" + filename + "'" + attributeInfo; } if (f.is_orphan) { return "**Orphaned LFN entries '" + filename + "'" + attributeInfo; } return filename + attributeInfo; } // FAT Cluster of data in a file typedef struct (uint size, ULONG lengthLeft ) { if( (lengthLeft >= size) || (lengthLeft <= 0) ) UBYTE data[ size ] ; else { UBYTE data[ lengthLeft ] ; UBYTE slack[ size - lengthLeft ] ; } } FAT_FILE_CLUSTER; // Extract starting cluster from a dir entry DWORD FAT_CalculateCluster( FAT_SHORTENTRY &entry, int DriveNum ) { // exFAT uses different types of directory entries, // so cannot use this function for exFAT Assert((FILE_SYSTEM_TYPE_FAT32 == drive[DriveNum].FsType) || (FILE_SYSTEM_TYPE_FAT16 == drive[DriveNum].FsType) || (FILE_SYSTEM_TYPE_FAT12 == drive[DriveNum].FsType)); if (drive[DriveNum].FsType == FILE_SYSTEM_TYPE_FAT32) { return ((DWORD)entry.HighCluster << 16) | entry.Cluster; // fat32 } return entry.Cluster; // FAT16 and FAT12 } // FAT File data displayed as clusters typedef struct { local int DriveNum = parentof(this).DriveNum; // keep track of which drive this belongs to local DWORD ClusterSize = drive[DriveNum].ClusterSize; local DWORD Cluster = FAT_CalculateCluster( parentof(this).short_entry, DriveNum ); local DWORD FirstCluster = Cluster; local DWORD MaxValidCluster = drive[DriveNum].MaxValidCluster; local ULONG SizeLeft = parentof(this).short_entry.FileSizeInBytes; // Define clusters while(1) { // Create one cluster FAT_JumpToCluster( Cluster, DriveNum ); FAT_FILE_CLUSTER cluster( ClusterSize, SizeLeft ); if( SizeLeft < ClusterSize ) SizeLeft = 0; else SizeLeft -= ClusterSize; // Jump to the next cluster Cluster = drive[DriveNum].table.Cluster[ Cluster ]; if( (Cluster == 0) || (Cluster >= MaxValidCluster) || (Cluster == FirstCluster) ) break; } } FAT_FILE_DATA ; // Use on-demand parsing for file data - do not load the data until the hierarchy is opened. // By default we just assume this is 512 in length because the actual data may not // be contiguous on disk, and this is the smallest supported sector size. int SizeFAT_FILE_DATA( FAT_FILE_DATA &dir ) { return 512; } // FAT Directory Entry List // HACKHACK -- This structure ***MUST*** be an "On-Demand" structure. // Therefore, do ***NOT*** set this to default to open. // See additional notes next to declaration of the XYZZY_* variables. typedef struct { local int DriveNum = parentof(this).DriveNum; // keep track of which drive this belongs to // Define all file entries XYZZY_Start = FTell(); while(1) { FAT_DIRECTORY_ENTRY_SET direntry; if (exists(direntry.short_entry) && direntry.short_entry.Name[0] == 0) // End of Directory Entry break; if (direntry.end_of_fat_chain) // end of the FAT chain, without an End-of-Directory Entry break; FSeek( XYZZY_Start ); // needed for non-contiguous directories } } FAT_DIRECTORY ; // Use on-demand parsing for directories - do not load the data until the hierarchy is opened. // By default we just assume this is 512 in length because the actual data may not // be contiguous on disk, and 512 is the smallest supported sector size. int SizeFAT_DIRECTORY( FAT_DIRECTORY &dir ) { return 512; } //################################################################ // NTFS Drives //################################################################ // Forward definition struct NTFS_FILE_RECORD; // NTFS Boot sector struct NTFS_BOOTSECTOR { BYTE jmp[3]; // Jump Instruction CHAR OEMName[8]; // OEM Identifier WORD BytesPerSector; BYTE SectorsPerCluster; WORD ReservedSectors; UBYTE Zero[3]; WORD NotUsed; MEDIA MediaDescriptor ; WORD Zero2; WORD SectorsPerTrack; WORD HeadsPerCylinder; DWORD HiddenSectors; DWORD NotUsed; DWORD NotUsed; UQUAD TotalSectors; UQUAD LogicalClusterMFT; UQUAD LogicalClusterMFTMirror; DWORD ClustersPerFileRecSegment; DWORD ClustersPerIndexBlock; UQUAD SerialNumber ; DWORD Checksum; BYTE BootCode[426]; WORD EndOfSectorMarker ; }; boolean IsValidNtfsBootSector(NTFS_BOOTSECTOR &boot) { if (boot.EndOfSectorMarker != 0xAA55) { return false; } if ((boot.BytesPerSector != 512) && (boot.BytesPerSector != 1024) && (boot.BytesPerSector != 2048) && (boot.BytesPerSector != 4096)) { return false; } if ((boot.SectorsPerCluster != 128) && (boot.SectorsPerCluster != 64) && (boot.SectorsPerCluster != 32) && (boot.SectorsPerCluster != 16) && (boot.SectorsPerCluster != 8) && (boot.SectorsPerCluster != 4) && (boot.SectorsPerCluster != 2) && (boot.SectorsPerCluster != 1)) { return false; } if (boot.Zero[0] != 0) { return false; } if (boot.Zero[1] != 0) { return false; } if (boot.Zero[2] != 0) { return false; } if (boot.Zero2 != 0) { return false; } if (boot.TotalSectors == 0) { return false; } if (boot.LogicalClusterMFT == 0) { return false; } if (boot.ClustersPerFileRecSegment == 0) { return false; } if (boot.ClustersPerIndexBlock == 0) { return false; } // TODO: Validate the Checksum? return true; } typedef enum { STANDARD_INFORMATION = 0x10, ATTRIBUTE_LIST = 0x20, FILE_NAME = 0x30, OBJECT_ID = 0x40, SECURITY_DESCRIPTOR = 0x50, VOLUME_NAME = 0x60, VOLUME_INFORMATION = 0x70, DATA = 0x80, INDEX_ROOT = 0x90, INDEX_ALLOCATION = 0xA0, BITMAP = 0xB0, REPARSE_POINT = 0xC0, EA_INFORMATION = 0xD0, EA = 0xE0, PROPERTY_SET = 0xF0, LOGGED_UTILITY_STREAM = 0x100 } NTFS_ATTR_TYPE; // NTFS Standard Information typedef struct { FILETIME CreationTime; FILETIME ModifiedTime; FILETIME MFTChangedTime; FILETIME FileReadTime; DWORD DosPermissions; DWORD MaximumVersions; DWORD VersionNumber; DWORD ClassID; DWORD OwnerID; DWORD SecurityID; UQUAD QuotaCharged; UQUAD UpdateSequenceNumber; } NTFS_ATTR_STANDARD; // NTFS Namspace - type of file name typedef enum { NAMESPACE_POSIX = 0, NAMESPACE_WIN32 = 1, NAMESPACE_DOS = 2, NAMESPACE_WIN32DOS = 3 } NTFS_NAMESPACE; // NTFS File Flags typedef struct { DWORD ReadOnly : 1; DWORD Hidden : 1; DWORD System : 1; DWORD : 1; // Unused DWORD : 1; DWORD Archive : 1; DWORD Device : 1; DWORD Normal : 1; DWORD Temp : 1; DWORD Sparse : 1; DWORD Reparse : 1; DWORD Compressed : 1; DWORD Offline : 1; DWORD NotIndexed : 1; DWORD Encrypted : 1; DWORD : 13; DWORD Directory : 1; DWORD IndexView : 1; } NTFS_FILE_FLAGS ; // Show some information beside the flag name string ReadNTFS_FILE_FLAGS( NTFS_FILE_FLAGS &flags ) { local string s ; if( flags.Directory ) s += "Directory "; if( flags.ReadOnly ) s += "ReadOnly "; if( flags.Hidden ) s += "Hidden "; if( flags.System ) s += "System "; return s; } //Names assigned to certain reparse point tags typedef enum { IO_REPARSE_TAG_RESERVED_ZERO=0x00000000, IO_REPARSE_TAG_RESERVED_ONE=0x00000001, IO_REPARSE_TAG_RESERVED_TWO=0x00000002, IO_REPARSE_TAG_MOUNT_POINT=0xA0000003, IO_REPARSE_TAG_HSM=0xC0000004, IO_REPARSE_TAG_DRIVE_EXTENDER=0x80000005, IO_REPARSE_TAG_HSM2=0x80000006, IO_REPARSE_TAG_SIS=0x80000007, IO_REPARSE_TAG_WIM=0x80000008, IO_REPARSE_TAG_CSV=0x80000009, IO_REPARSE_TAG_DFS=0x8000000A, IO_REPARSE_TAG_FILTER_MANAGER=0x8000000B, IO_REPARSE_TAG_SYMLINK=0xA000000C, IO_REPARSE_TAG_IIS_CACHE=0xA0000010, IO_REPARSE_TAG_DFSR=0x80000012, IO_REPARSE_TAG_DEDUP=0x80000013, IO_REPARSE_TAG_APPXSTRM=0xC0000014, IO_REPARSE_TAG_NFS=0x80000014, IO_REPARSE_TAG_FILE_PLACEHOLDER=0x80000015, IO_REPARSE_TAG_DFM=0x80000016, IO_REPARSE_TAG_WOF=0x80000017, IO_REPARSE_TAG_WCI=0x80000018, IO_REPARSE_TAG_WCI_1=0x90001018, IO_REPARSE_TAG_GLOBAL_REPARSE=0xA0000019, IO_REPARSE_TAG_CLOUD=0x9000001A, IO_REPARSE_TAG_CLOUD_1=0x9000101A, IO_REPARSE_TAG_CLOUD_2=0x9000201A, IO_REPARSE_TAG_CLOUD_3=0x9000301A, IO_REPARSE_TAG_CLOUD_4=0x9000401A, IO_REPARSE_TAG_CLOUD_5=0x9000501A, IO_REPARSE_TAG_CLOUD_6=0x9000601A, IO_REPARSE_TAG_CLOUD_7=0x9000701A, IO_REPARSE_TAG_CLOUD_8=0x9000801A, IO_REPARSE_TAG_CLOUD_9=0x9000901A, IO_REPARSE_TAG_CLOUD_A=0x9000A01A, IO_REPARSE_TAG_CLOUD_B=0x9000B01A, IO_REPARSE_TAG_CLOUD_C=0x9000C01A, IO_REPARSE_TAG_CLOUD_D=0x9000D01A, IO_REPARSE_TAG_CLOUD_E=0x9000E01A, IO_REPARSE_TAG_CLOUD_F=0x9000F01A, IO_REPARSE_TAG_APPEXECLINK=0x8000001B, IO_REPARSE_TAG_PROJFS=0x9000001C, IO_REPARSE_TAG_LX_SYMLINK=0xA000001D, IO_REPARSE_TAG_STORAGE_SYNC=0x8000001E, IO_REPARSE_TAG_WCI_TOMBSTONE=0xA000001F, IO_REPARSE_TAG_UNHANDLED=0x80000020, IO_REPARSE_TAG_ONEDRIVE=0x80000021, IO_REPARSE_TAG_PROJFS_TOMBSTONE=0xA0000022, IO_REPARSE_TAG_AF_UNIX=0x80000023, IO_REPARSE_TAG_LX_FIFO=0x80000024, IO_REPARSE_TAG_LX_CHR=0x80000025, IO_REPARSE_TAG_LX_BLK=0x80000026, IO_REPARSE_TAG_WCI_LINK=0xA0000027, IO_REPARSE_TAG_WCI_LINK_1=0xA0001027 } IO_REPARSE_TAG; // NTFS Reparse Point Tags // This is a breakout of the contents of IO_REPARSE_TAG values // This give details of what the tag is composed of but loses the // arbitrary names so both alternatives are presented typedef struct { local uint64 dwordStart ; dwordStart = FTell(); FSeek(dwordStart + 3); BitfieldDisablePadding(); BigEndian(); UBYTE microsoftOwnedTag: 1; UBYTE microsoftReserved1: 1; UBYTE nameSurrogateBit: 1; UBYTE microsoftReserved2: 1; //there are 12 reserved bits that are all 0 for all current tags LittleEndian(); BitfieldEnablePadding(); FSeek(dwordStart); USHORT ReparseTagValue; FSeek(dwordStart + 4); } FILE_ATTRIBUTE_REPARSE_POINT; // NTFS File Name Information typedef struct { local int64 start = FTell(); BitfieldDisablePadding(); UQUAD FileRecordNumber : 48; UQUAD SequenceNumber : 16; BitfieldEnablePadding(); FILETIME CreationTime; FILETIME ModifiedTime; FILETIME MFTChangedTime; FILETIME FileReadTime; UQUAD AllocateSize; UQUAD RealSize; NTFS_FILE_FLAGS Flags; DWORD Reparse; UBYTE FileNameLength; NTFS_NAMESPACE Namespace; if( FileNameLength > 0 ) { wchar_t FileName[ FileNameLength ]; } } NTFS_ATTR_FILE_NAME; // NTFS Volume Name typedef struct (int length) { wchar_t VolumeName[length/2]; } NTFS_ATTR_VOLUME_NAME; // NTFS Volume Information typedef struct { UQUAD Empty1; UCHAR MajorVersion; UCHAR MinorVersion; WORD Flags; DWORD Empty2; } NTFS_ATTR_VOLUME_INFO; // NTFS Index Root typedef struct { DWORD AttributeType; DWORD CollationRule; DWORD IndexAllocationEntrySize; DWORD ClustersPerIndexRecord; DWORD FirstIndexEntryOffset; DWORD IndexEntriesSize; DWORD IndexEntriesAllocated; DWORD HasLargeIndex : 1; DWORD : 31; } NTFS_ATTR_INDEX_ROOT; // NTFS Index Entry Header typedef struct { UQUAD FileRecordNumber : 48; UQUAD SequenceNumber : 16; WORD IndexEntryLength; WORD StreamLength; UBYTE HasSubNode : 1; UBYTE IsLastEntry : 1; UBYTE : 6; UBYTE Padding[3]; } NTFS_ATTR_INDEX_ENTRY_HEADER; // NTFS Index Entry typedef struct { local int64 start = FTell(); NTFS_ATTR_INDEX_ENTRY_HEADER header; if( !header.IsLastEntry && (header.StreamLength > 0) ) { UBYTE Stream[header.StreamLength]; } if( header.HasSubNode ) { FSeek( start + header.IndexEntryLength - 8 ); UQUAD SubNode; } // Jump to the end of the record FSeek( start + header.IndexEntryLength ); } NTFS_ATTR_INDEX_ENTRY ; // NTFS File Name Index Entry typedef struct { local int64 start = FTell(); NTFS_ATTR_INDEX_ENTRY_HEADER header; if( !header.IsLastEntry && (header.StreamLength > 0) ) { NTFS_ATTR_FILE_NAME fileNameAttr; } if( header.HasSubNode ) { FSeek( start + header.IndexEntryLength - 8 ); UQUAD SubNode; } // Jump to the end of the record FSeek( start + header.IndexEntryLength ); } NTFS_INDEX_FILE_NAME_ENTRY ; // Display file name and subnode beside index entry wstring ReadNTFS_INDEX_FILE_NAME_ENTRY( NTFS_INDEX_FILE_NAME_ENTRY &fn ) { local string s ; if( exists( fn.fileNameAttr ) ) { s = fn.fileNameAttr.FileName; } if( fn.header.HasSubNode ) { local string subnode ; SPrintf( subnode, " (Subnode %d)", fn.SubNode ); s += subnode; } return s; } // NTFS Run - define a block of data (starting VCN and size) typedef struct { UBYTE LengthFieldSize : 4; UBYTE OffsetFieldSize : 4; BitfieldDisablePadding(); UQUAD RunLength : LengthFieldSize*8; UQUAD LCNOffset : OffsetFieldSize*8; // Logical Cluster Number BitfieldEnablePadding(); } NTFS_RUN; // NTFS Run list - define where file data exists on disk typedef struct ( UQUAD maxPos ) { while( (ReadUByte( FTell() ) != 0) && (FTell() < maxPos) ) { NTFS_RUN run; } } NTFS_RUN_LIST ; // NTFS File Index typedef struct { local int64 startPos = FTell(); local int i ; struct NTFS_INDEX_HEADER { UCHAR Magic[4]; WORD UpdateSequenceOffset; WORD UpdateSequenceSize; UQUAD LogFileSequence; UQUAD VCN; // Virtual Cluster Number DWORD IndexEntriesOffset; DWORD IndexEntriesSize; DWORD IndexEntriesAllocated; DWORD HasChildren; WORD UpdateSequence; if( UpdateSequenceSize > 0 ) { WORD UpdateSequenceArray[UpdateSequenceSize]; } } header; // Create the list of file name entries here if( header.Magic == "INDX" ) { FSeek( startPos + 0x18 + header.IndexEntriesOffset ); do { NTFS_INDEX_FILE_NAME_ENTRY entry; } while( (!entry.header.IsLastEntry) && (entry.header.IndexEntryLength > 0) && (FTell() - startPos < header.IndexEntriesSize + header.IndexEntriesOffset) ); } } NTFS_FILE_INDEX ; // Convert from an lcn (logical cluster number) to an address in the file UQUAD NTFS_LCNToAddress( UQUAD lcn, int DriveNum ) { return drive[DriveNum].DriveStart + lcn * drive[DriveNum].ClusterSize; } // NTFS File Block - store a block of data in a file typedef struct (int size, UQUAD lengthLeft) { if( lengthLeft >= size ) UBYTE data[ size ]; else { UBYTE data[ lengthLeft ]; UBYTE slack[ size - lengthLeft ]; } } NTFS_FILE_BLOCK; // NTFS File data - stores information from the file on disk // as a series of blocks - could be located in different places // in the file. typedef struct { local int i ; local UQUAD vcn = 0; local UQUAD RunLength ; local int DriveNum = parentof(parentof(parentof(this))).DriveNum; local UQUAD LengthLeft = parentof(this).header.AttributeSize; while( exists( parentof(this).runList.run[i] ) ) { // Create the block of data using the run list FSeek( NTFS_LCNToAddress( NTFS_VCNToLCN( parentof(this).runList, vcn ), DriveNum ) ); RunLength = parentof(this).runList.run[i].RunLength; vcn += RunLength; NTFS_FILE_BLOCK block( RunLength * drive[DriveNum].ClusterSize, LengthLeft ); LengthLeft -= RunLength * drive[DriveNum].ClusterSize; i++; } } NTFS_FILE_DATA ; // set size as 1024 as could be disjoint structures // NTFS Attribute typedef struct { local int64 start = FTell(); local int i ; struct NTFS_ATTRIBUTE_HEADER { NTFS_ATTR_TYPE Type; DWORD Length; UBYTE NonResident; UBYTE NameLength; WORD NameOffset; WORD IsCompressed : 1; WORD : 13; WORD IsEncrypted : 1; WORD IsSparse : 1; WORD AttributeID; if( NonResident ) { UQUAD StartingVCN; UQUAD LastVCN; WORD DataRunsOffset; WORD CompressionUnitSize; UBYTE Padding[4]; UQUAD AttributeAllocated; UQUAD AttributeSize; UQUAD StreamDataSize; } else { DWORD AttributeLength; WORD AttributeOffset; UBYTE IndexedFlag; UBYTE Padding; } if( NameLength > 0 ) { FSeek( start + NameOffset ); wchar_t Name[NameLength]; } } header; if( !header.NonResident ) { // Resident attributes FSeek( start + header.AttributeOffset ); if( header.Type == STANDARD_INFORMATION ) { NTFS_ATTR_STANDARD standardInformation; } else if( header.Type == FILE_NAME ) { NTFS_ATTR_FILE_NAME fileName; } else if( header.Type == VOLUME_NAME ) { NTFS_ATTR_VOLUME_NAME volumeName( header.AttributeLength ); } else if( header.Type == VOLUME_INFORMATION ) { NTFS_ATTR_VOLUME_INFO volumeInfo; } else if( header.Type == INDEX_ROOT ) { // Stores the root nodes of the btree index for file names NTFS_ATTR_INDEX_ROOT indexRoot; do { NTFS_INDEX_FILE_NAME_ENTRY indexEntry; } while( !indexEntry.header.IsLastEntry && (indexEntry.header.IndexEntryLength > 0) && (FTell() - start < header.Length) ); } else if( header.Type == DATA ) { if( (header.NameLength > 0) && (header.Name == "$Max") ) { // Added to parse UsnJrnl $Max struct UQUAD MaximumSize; UQUAD AllocationSize; FILETIME UsnID; UQUAD LowestValidUsn; } else if( header.AttributeLength > 0 ) { // File data is stored directly in the header for small files ubyte fileData[ header.AttributeLength ]; } } else if( header.Type == REPARSE_POINT ) { local uint64 ReparseTagStart ; ReparseTagStart = FTell(); IO_REPARSE_TAG reparseTag; FSeek(ReparseTagStart); FILE_ATTRIBUTE_REPARSE_POINT reparseTagAttributes; UBYTE reparseData[ header.AttributeLength - 4 ]; } } else { // Non-resident attribute - data is in a different place in the // drive and use runlist to locate FSeek( start + header.DataRunsOffset ); NTFS_RUN_LIST runList( start + header.Length ); if( (header.Type == INDEX_ALLOCATION) && (parentof(this).header.IsDirectory) ) { // Stores an index (btree) of file names for( i = 0; i <= header.LastVCN; i++ ) { FSeek( NTFS_LCNToAddress( NTFS_VCNToLCN( runList, i ), parentof(parentof(this)).DriveNum ) ); if( ReadString( FTell(), 4 ) != "INDX" ) // make sure header is there { break; } NTFS_FILE_INDEX index; } } else if( exists(runList.run[0].LCNOffset) ) { // Generic data - use the file data struct to read the data FSeek( NTFS_LCNToAddress( runList.run[0].LCNOffset, parentof(parentof(this)).DriveNum ) ); NTFS_FILE_DATA data; } } // Jump to the end of the record FSeek( start + header.Length ); } NTFS_ATTRIBUTE ; // Display data beside the attribute string ReadNTFS_ATTRIBUTE( NTFS_ATTRIBUTE &attr ) { string s; if( attr.header.Type == FILE_NAME ) { SPrintf( s, "%s = %s", EnumToString( attr.header.Type ), attr.fileName.FileName ); } else if( attr.header.Type == VOLUME_NAME ) { SPrintf( s, "%s = %s", EnumToString( attr.header.Type ), attr.volumeName.VolumeName ); } else { s = EnumToString( attr.header.Type ); } if( attr.header.NonResident ) { s += " (Non-Resident)"; } return s; } // Used to sign-extend run values local const UQUAD NTFS_SignExtend[8] = { 0xffffffffffffff00, 0xffffffffffff0000, 0xffffffffff000000, 0xffffffff00000000, 0xffffff0000000000, 0xffff000000000000, 0xff00000000000000, 0x0000000000000000 }; // Function to convert from a virtual cluster number (file cluster number) // to a logical cluster number (drive cluster number). The file contains a // number of runs and check which run the vcn belongs to. UQUAD NTFS_VCNToLCN( NTFS_RUN_LIST &runlist, UQUAD vcn ) { local int i ; local UQUAD offset = 0; while( exists( runlist.run[i] ) ) { // Move ahead using LCNOffset if( ReadUByte( startof(runlist.run[i].LCNOffset)+runlist.run[i].OffsetFieldSize-1 ) >= 0x80 ) { // Offset is negative - sign extend offset += (QUAD)(runlist.run[i].LCNOffset + NTFS_SignExtend[runlist.run[i].OffsetFieldSize-1] ); } else { // Offset is positive offset += runlist.run[i].LCNOffset; } // Check if vcn is in this run if( vcn < runlist.run[i].RunLength ) return vcn + offset; else { vcn -= runlist.run[i].RunLength; i++; } } return 0xFFFFFFFFFFFFFFFFL; // not found } // Function to iterate through the file name index (btree) // and list the files UQUAD NTFS_ListFiles( NTFS_INDEX_FILE_NAME_ENTRY &entry, NTFS_ATTRIBUTE &allocation, int DriveNum, int level, UQUAD previousFile ) { // Step into sub-node - watch for infinite recursion local int k = 0; local int dataAttr ; local int fileRecsPerCluster ; local UQUAD lcn ; if( entry.header.HasSubNode && (level < 10) ) { while( 1 ) { previousFile = NTFS_ListFiles( allocation.index[ entry.SubNode ].entry[k], allocation, DriveNum, level+1, previousFile ); if( allocation.index[ entry.SubNode ].entry[k].header.IsLastEntry ) break; k++; } } // Create file record if( entry.header.StreamLength > 0 ) { // Avoid creating the same file twice if( previousFile != entry.header.FileRecordNumber ) { // Locate the file position - have to use the runlist of the mft previousFile = entry.header.FileRecordNumber; dataAttr = NTFS_FindAttribute( drive[DriveNum].mft.mft[0], DATA ); fileRecsPerCluster = drive[DriveNum].ClusterSize / 1024; lcn = NTFS_VCNToLCN( drive[DriveNum].mft.mft[0].attribute[dataAttr].runList, entry.header.FileRecordNumber/fileRecsPerCluster ); FSeek( NTFS_LCNToAddress( lcn, DriveNum ) + (entry.header.FileRecordNumber % fileRecsPerCluster)*1024 ); if( ReadString( FTell(), 4 ) == "FILE" ) // make sure header is there { NTFS_FILE_RECORD file; } else { DWORD invalid; // file record not found } } } return previousFile; } // Find an attribute in a file record int NTFS_FindAttribute( NTFS_FILE_RECORD &rec, NTFS_ATTR_TYPE type ) { local int i ; while( exists(rec.attribute[i]) ) { if( rec.attribute[i].header.Type == type ) { return i; } i++; } return -1; // not found } // NTFS Directory typedef struct { local int DriveNum = parentof(parentof(this)).DriveNum; // keep track of which drive this belongs to local int dirRoot = NTFS_FindAttribute( parentof(this), INDEX_ROOT ); local int dirAllocation = NTFS_FindAttribute( parentof(this), INDEX_ALLOCATION ); local UQUAD previousFile = 0xFFFFFFFFFFFFFFFFL; local int i = 0; local int64 start = FTell(); while( 1 ) { // Iterate through the b-tree index and create the list of files if( dirAllocation < 0 ) dirAllocation = 0; // no allocation - everything stored in INDEX_ROOT previousFile = NTFS_ListFiles( parentof(this).attribute[dirRoot].indexEntry[i], attribute[dirAllocation], DriveNum, 0, previousFile ); if( parentof(this).attribute[dirRoot].indexEntry[i].header.IsLastEntry ) break; i++; } FSeek( start + 1024 ); } NTFS_DIRECTORY ; // NTFS File Record typedef struct { local int64 start = FTell(); local int attrRoot ; // Header info struct FILE_RECORD_HEADER_NTFS { BYTE Magic[4]; WORD UpdateSequenceOffset; WORD UpdateSequenceSize; UQUAD LogFileSequenceNumber; WORD SequenceNumber; WORD HardLinkCount; WORD FirstAttributeOffset; WORD InUse : 1; WORD IsDirectory : 1; DWORD UsedSize; DWORD AllocateSize; UQUAD FileReference; WORD NextAttributeID; WORD Align; DWORD MFTRecordNumber; if( FirstAttributeOffset >= 0x38 ) { UBYTE EndTag[2]; UBYTE FixupArray[6]; } } header; // Check header if( header.Magic != "FILE" ) { return; } // Read list of attributes FSeek( start + header.FirstAttributeOffset ); while( (ReadUInt(FTell()) != 0xffffffff) && (FTell() - start < 1024) ) { NTFS_ATTRIBUTE attribute; } // Add padding if necessary if( FTell() - start < 1024 ) { UBYTE padding[ 1024 - (FTell() - start) ]; } // Check if this is a directory attrRoot = NTFS_FindAttribute( this, INDEX_ROOT ); if( (attrRoot >= 0) && (header.IsDirectory) ) { FSeek( start ); NTFS_DIRECTORY sub_dir; } // Jump to the end of the record FSeek( start + 1024 ); } NTFS_FILE_RECORD ; //, size=1024>; // Display file name beside the file record wstring ReadNTFS_FILE_RECORD( NTFS_FILE_RECORD &fr ) { local string name ; local int attrNum = NTFS_FindAttribute( fr, FILE_NAME ); local int hidden ; local int system ; if( attrNum != -1 ) { // Skip short DOS names if( (fr.attribute[attrNum].fileName.Namespace == NAMESPACE_DOS) && exists( fr.attribute[attrNum+1].fileName.FileName ) ) { attrNum++; } name = fr.attribute[attrNum].fileName.FileName; // Check for hidden, system hidden = fr.attribute[attrNum].fileName.Flags.Hidden; system = fr.attribute[attrNum].fileName.Flags.System; if( hidden || system ) { name += " ("; if( hidden ) { name += "Hidden"; if( system ) name += " "; } if( system ) name += "System"; name += ")"; } // Check if this is a directory if( fr.header.IsDirectory ) name = "/" + name; // Check if this is deleted if( !fr.header.InUse ) name = "**Erased name:" + name; } return name; } // NTFS Master File Table (MFT) typedef struct { local int DriveNum = parentof(this).DriveNum; // keep track of which drive this belongs to NTFS_FILE_RECORD mft[16] ; } NTFS_MASTER_FILE_TABLE ; // Display the volume label beside the master file table string ReadNTFS_MASTER_FILE_TABLE( NTFS_MASTER_FILE_TABLE &mft ) { local int volAttr ; if( exists( mft.mft[3] ) ) { volAttr = NTFS_FindAttribute( mft.mft[3], VOLUME_NAME ); if( volAttr != -1 ) { return mft.mft[3].attribute[ volAttr ].volumeName.VolumeName; } } return ""; } // Define an NTFS Drive typedef struct { local int DriveNum = NumDrives++; // keep track of the index of this drive local FILE_SYSTEM_TYPE FsType = FILE_SYSTEM_TYPE_NTFS; // local BYTE DriveBytesPerSectorShift = BytesPerSectorShift; // BUGBUG -- validate 4k sector support, then enable parameterization of NTFS_DRIVE local int64 DriveStart = FTell(); local DWORD ClusterSize; // NTFS Boot sector NTFS_BOOTSECTOR boot_ntfs ; // Master File Table (MFT) ClusterSize = boot_ntfs.BytesPerSector * boot_ntfs.SectorsPerCluster; FSeek( DriveStart + boot_ntfs.LogicalClusterMFT * ClusterSize ); NTFS_MASTER_FILE_TABLE mft; // Root directory - jump to '.' in the master file table // this is repeated from the mft but makes it easier to find here FSeek( startof(mft) + 5*1024 ); NTFS_FILE_RECORD root_dir; } NTFS_DRIVE ; //################################################################ // HFS Drives (macOS/iOS) //################################################################ typedef char SInt8; typedef uchar UInt8; typedef int16 SInt16; typedef uint16 UInt16; typedef int32 SInt32; typedef uint32 UInt32; typedef int64 SInt64; typedef uint64 UInt64; // HFS block of data in a file struct HFS_ExtentDescriptor { UInt32 startBlock; UInt32 blockCount; }; // HFS describes the location of file data struct HFS_ForkData { UInt64 logicalSize; UInt32 clumpSize; UInt32 totalBlocks; HFS_ExtentDescriptor extents[8]; }; // HFS_Time - 32-bit integer, number of seconds since 01/01/1904 00:00:00 typedef uint HFS_Time ; string HFSTimeRead( HFS_Time t ) { // Convert to FILETIME if( t == 0 ) { return "-"; } else { return FileTimeToString( t*10000000L + 95616288000000000L ); } } int HFSTimeWrite( HFS_Time &t, string value ) { // Convert from FILETIME FILETIME ft; int result = StringToFileTime( value, ft ); t = (int)(((uint64)ft - 95616288000000000L)/10000000L); return result; } // HFS Catalog Node ID number typedef enum { kHFSRootParentID = 1, kHFSRootFolderID = 2, kHFSExtentsFileID = 3, kHFSCatalogFileID = 4, kHFSBadBlockFileID = 5, kHFSAllocationFileID = 6, kHFSStartupFileID = 7, kHFSAttributesFileID = 8, kHFSRepairCatalogFileID = 14, kHFSBogusExtentFileID = 15, kHFSFirstUserCatalogNodeID = 16 } HFS_CatalogNodeID; // HFS Record type typedef enum { kHFSFolderRecord = 0x0001, kHFSFileRecord = 0x0002, kHFSFolderThreadRecord = 0x0003, kHFSFileThreadRecord = 0x0004 } HFS_RecordType; // https://developer.apple.com/library/archive/documentation/mac/Files/Files-101.html#HEADING101-0 /* struct HFS_BOOTSECTOR { // Bytes 00 .. 07 UInt16 Identifier; // always 0x4C4B (or 0x4B4C ... have to check endianness UInt32 JmpCode; // machine-language that translate to 'BRA.S *+$90' or 'BRA.S *+$88' UInt16 Version; // flags in high-order byte, plus version of the boot block structure // Bytes 08 .. 09 UInt16 PageFlags; // "used internally" // Bytes 0A .. 39 char SystemFilename[16]; char FinderFilename[16]; // Usually "Finder" char DebuggerFilename1[16]; // Ususally "Macsbug" // Bytes 4A .. 79 char DebuggerFilename2[16]; // Ususally "Disassembler" char ScreenName[16]; // name of startup screen, usually "StartUpScreen" char HelloName[16]; // name of startup program, usually "Finder" char ScrapName[16]; // name of system scrap file, usually "Clipboard" // Bytes 8A .. 8B UInt16 FcbAllocationCount; // Initial count of file control blocks to allocate // Bytes 8C .. 8D UInt16 EventQueueElementCount; // Initial count of event queue elements to allocate, usually 20 (14h) // Bytes 8E .. 91 UInt32 HeapSize128k; // The size of the system heap on a Macintosh computer having 128k of RAM // Bytes 92 .. 95 UInt32 HeapSize256k; // "Reserved" (!) // Bytes 96 .. 99 UInt32 SystemHeapSize; // The size of the system heap on a Macintosh computer having 512k or more of RAM // Bytes 9A .. 9B UInt16 Filler; // reserved // Bytes 9C .. A0 UInt32 SystemHeapExtraSpace; // Valid when bit 5 of high-order byte of flags field is set. Minimum additional amount of additional System heap space required. // Bytes A0 .. A3 UInt32 SystemHeapFraction; // Valid when bit 5 of high-order byte of flags field is set. Fraction of RAM available to be used for the System heap, and added to SystemHeapSize. // Bytes A4 .. 3FF (164 .. 1023) Byte BootCode[860]; // pad to 1024 bytes with boot code }; */ // HFS Volume information struct HFS_VolumeHeader { uchar reserved[1024]; // TODO: replace with HFS_BOOTSECTOR char signature[2]; // 0x4244 UInt16 version; UInt32 attributes; char lastMountedVersion[4]; UInt32 journalInfoBlock; HFS_Time createDate; HFS_Time modifyDate; HFS_Time backupDate; HFS_Time checkedDate; UInt32 fileCount; UInt32 folderCount; UInt32 blockSize; UInt32 totalBlocks; UInt32 freeBlocks; UInt32 nextAllocation; UInt32 rsrcClumpSize; UInt32 dataClumpSize; HFS_CatalogNodeID nextCatalogID; UInt32 writeCount; UInt64 encodingsBitmap; UInt32 finderInfo[8]; HFS_ForkData allocationFile; HFS_ForkData extentsFile; HFS_ForkData catalogFile; HFS_ForkData attributesFile; HFS_ForkData startupFile; }; boolean IsValidHfsBootSector(int64 pos) { // For details on the HFS boot sector, see: // https://developer.apple.com/library/archive/documentation/mac/Files/Files-101.html#HEADING101-0 if( (ReadString( pos + 1024, 2 ) != "H+") && (ReadString( pos + 1024, 2 ) != "HX") ) { return false; } // TODO: Add additional HFS validation return true; } // HFS Unicode string struct HFS_UniStr255 { UInt16 length; if( length > 0 ) { wchar_t unicode[length]; } }; // HFS Unix style permissions struct HFS_BSDInfo { UInt32 ownerID; UInt32 groupID; UInt8 adminFlags; UInt8 ownerFlags; UInt16 fileMode ; union { UInt32 iNodeNum; UInt32 linkCount; UInt32 rawDevice; } special; }; // HFS 2d point struct HFS_Point { SInt16 v; SInt16 h; }; typedef char HFS_OSType[4]; // HFS File information struct HFS_FileInfo { HFS_OSType fileType; // The type of the file HFS_OSType fileCreator; // The file's creator UInt16 finderFlags; HFS_Point location; // File's location in the folder. UInt16 reservedField; }; // HFS Extended file information struct HFS_ExtendedFileInfo { SInt16 reserved1[4]; UInt16 extendedFinderFlags; SInt16 reserved2; SInt32 putAwayFolderID; }; // HFS rectangle struct HFS_Rect { SInt16 top; SInt16 left; SInt16 bottom; SInt16 right; }; // HFS Folder information struct HFS_FolderInfo { HFS_Rect windowBounds; // The position and dimension of the folder's window UInt16 finderFlags; HFS_Point location; // Folder's location in the parent // folder. If set to {0, 0}, the Finder // will place the item automatically UInt16 reservedField; }; // HFS Extended folder information struct HFS_ExtendedFolderInfo { HFS_Point scrollPosition; // Scroll position (for icon views) SInt32 reserved1; UInt16 extendedFinderFlags; SInt16 reserved2; SInt32 putAwayFolderID; }; // HFS - Key information for a node stored in the catalog btree typedef struct { UInt16 keyLength; HFS_CatalogNodeID parentID; HFS_UniStr255 nodeName; } HFS_CatalogKey ; wstring ReadHFSCatalogKey( HFS_CatalogKey &key ) { if( key.nodeName.length > 0 ) return key.nodeName.unicode; return ""; } // HFS - Folder information stored in the catalog typedef struct { HFS_RecordType recordType; UInt16 flags; UInt32 valence; HFS_CatalogNodeID folderID; HFS_Time createDate; HFS_Time contentModDate; HFS_Time attributeModDate; HFS_Time accessDate; HFS_Time backupDate; HFS_BSDInfo permissions; HFS_FolderInfo userInfo; HFS_ExtendedFolderInfo finderInfo; UInt32 textEncoding; UInt32 reserved; } HFS_CatalogFolder; // Forward definitions struct HFS_BTNode; struct HFS_Folder; struct HFS_File; // HFS - Function to iterate through the btree and list all folders and // files with the given parent id void HFS_ListFilesInNode( HFS_BTNode &node, uint folderID ) { local int i , type ; local int64 pos ; local uint nextID ; local int count = node.descriptor.numRecords; if( node.descriptor.kind == kBTIndexNode ) { // Traverse down the index nodes looking for the proper parent id for( i = 0; i < count; i++ ) { if( i < count-1 ) nextID = node.record[i+1].key.parentID; else nextID = 0xffffffff; if( (node.record[i].key.parentID <= folderID) && (folderID <= nextID) ) { // Traverse down this node if( exists( node.record[i].childNode ) ) HFS_ListFilesInNode( node.record[i].childNode, folderID ); } else if( node.record[i].key.parentID > folderID ) break; } } else if( node.descriptor.kind == kBTLeafNode ) { // Create a copy of all folders and files for the directory structure for( i = 0; i < node.descriptor.numRecords; i++ ) { if( exists( node.record[i] ) ) { if( node.record[i].key.parentID == folderID ) { pos = startof( node.record[i] ); FSeek( pos ); BigEndian(); type = ReadUShort( pos + ReadUShort( pos+6 )*2 + 8 ); if( type == kHFSFolderRecord ) HFS_Folder folder; else if( type == kHFSFileRecord ) HFS_File file; LittleEndian(); } } } } } void HFS_ListFiles( int DriveNum, uint folderID ) { HFS_ListFilesInNode( drive[DriveNum].btree.rootNode, folderID ); } // HFS List of files and subfolders inside a folder typedef struct { local int DriveNum = parentof(this).DriveNum; local int64 pos = FTell(); BigEndian(); HFS_ListFiles( parentof(this).DriveNum, parentof(this).folderInfo.folderID ); FSeek( pos+4 ); LittleEndian(); } HFS_FolderList ; //use on-demand - size unknown // HFS - Folder stored in the catalog typedef struct { local int DriveNum = parentof(this).DriveNum; HFS_CatalogKey key; HFS_CatalogFolder folderInfo; // Store link to all files in this folder local int64 pos = FTell(); HFS_FolderList subDir; FSeek( pos ); } HFS_Folder ; wstring ReadHFSFolder( HFS_Folder &rec ) { if( rec.key.nodeName.length > 0 ) { return "/" + rec.key.nodeName.unicode; } return ""; } // HFS - File information stored in the catalog typedef struct { HFS_RecordType recordType; UInt16 flags; UInt32 reserved1; HFS_CatalogNodeID fileID; HFS_Time createDate; HFS_Time contentModDate; HFS_Time attributeModDate; HFS_Time accessDate; HFS_Time backupDate; HFS_BSDInfo permissions; HFS_FileInfo userInfo; HFS_ExtendedFileInfo finderInfo; UInt32 textEncoding; UInt32 reserved2; HFS_ForkData dataFork; HFS_ForkData resourceFork; } HFS_CatalogFile ; // HFS - Block of data in a file typedef struct (int size, uint64 lengthLeft) { if( lengthLeft >= size ) { UBYTE data[ size ]; } else { UBYTE data[ lengthLeft ]; UBYTE slack[ size - lengthLeft ]; } } HFS_FileBlock; // HFS File Data - list as a series of blocks typedef struct { local int DriveNum = parentof(this).DriveNum; local int LengthLeft = parentof(this).fileInfo.dataFork.logicalSize; local int blockSize = drive[DriveNum].header.blockSize; local int i , size ; local int64 pos = FTell(); BigEndian(); for( i = 0; i < 8; i++ ) { // Create a block at this extents size = parentof(this).fileInfo.dataFork.extents[i].blockCount * blockSize; if( size == 0 ) break; FSeek( HFS_BlockToAddress( DriveNum, parentof(this).fileInfo.dataFork.extents[i].startBlock ) ); HFS_FileBlock block( size, LengthLeft ); LengthLeft -= size; if( LengthLeft <= 0 ) break; } // NOTE: In the future we could read from the extents overflow file for // data files that have more than 8 extents // BUGBUG-4kn -- magic required here to support 4k sectors on HFS FSeek( pos + blockSize ); LittleEndian(); } HFS_FileData ; //use on-demand - size unknown // HFS - File stored in the catalog typedef struct { local int DriveNum = parentof(this).DriveNum; HFS_CatalogKey key; HFS_CatalogFile fileInfo; // Store the file data is a set of blocks if( fileInfo.dataFork.logicalSize > 0 ) { local int64 pos = FTell(); FSeek( HFS_BlockToAddress( DriveNum, fileInfo.dataFork.extents[0].startBlock ) ); HFS_FileData fileData; FSeek( pos ); } } HFS_File ; wstring ReadHFSFile( HFS_File &rec ) { if( rec.key.nodeName.length > 0 ) { return rec.key.nodeName.unicode; } return ""; } // HFS - Index record in the catalog typedef struct { local int DriveNum = parentof(this).DriveNum; HFS_CatalogKey key; UInt32 link; // Store on-demand child node local int64 pos = FTell(); if( HFS_JumpToCatalogNode( DriveNum, link ) ) HFS_BTNode childNode; FSeek( pos ); } HFS_CatalogRecord ; wstring ReadHFSCatalogRecord( HFS_CatalogRecord &rec ) { if( rec.key.nodeName.length > 0 ) return rec.key.nodeName.unicode; return ""; } // HFS Node kind typedef enum { kBTLeafNode = -1, kBTIndexNode = 0, kBTHeaderNode = 1, kBTMapNode = 2 } HFS_NodeKind; // HFS Descriptor for each node of the btree struct HFS_BTNodeDescriptor { UInt32 fLink; UInt32 bLink; HFS_NodeKind kind; UInt8 height; UInt16 numRecords; UInt16 reserved; }; // HFS Main information for the btree header node struct HFS_BTHeaderRec { UInt16 treeDepth; UInt32 rootNode; UInt32 leafRecords; UInt32 firstLeafNode; UInt32 lastLeafNode; UInt16 nodeSize; UInt16 maxKeyLength; UInt32 totalNodes; UInt32 freeNodes; UInt16 reserved1; UInt32 clumpSize; // misaligned UInt8 btreeType; UInt8 keyCompareType; UInt32 attributes; // long aligned again UInt32 reserved3[16]; }; // HFS Header node of the btree struct HFS_BTHeaderNode { HFS_BTNodeDescriptor descriptor; HFS_BTHeaderRec header; UInt8 userDataRecord[128]; UInt8 map[ header.nodeSize - 256 ]; UInt16 offsets[4]; }; // HFS Node of the btree typedef struct { local int j ; local int64 startPos = FTell(); local SInt16 recordType ; // Node descriptor BigEndian(); HFS_BTNodeDescriptor descriptor; local int DriveNum = parentof(parentof(descriptor)).DriveNum; // BUGBUG/HACKHACK -- is this a way to handle optimized array? local int NodeSize = drive[DriveNum].btree.headerNode.header.nodeSize; // Create each record of the node for( j = 0; j < descriptor.numRecords; j++ ) { FSeek( startPos + ReadUShort(startPos + NodeSize - 2 - j*2) ); if( descriptor.kind == kBTIndexNode ) { // Create index nodes HFS_CatalogRecord record; } else if( descriptor.kind == kBTLeafNode ) { // Create leaf nodes - either file or folder recordType = ReadUShort( FTell() + ReadUShort( FTell()+6 )*2 + 8 ); if( recordType == kHFSFolderRecord ) { HFS_Folder record; } else if( recordType == kHFSFileRecord ) { HFS_File record; } } } LittleEndian(); } HFS_BTNode ; // use on-demand structure // HFS - Function to convert from a local block number to a drive block number using the extents int64 HFS_LocalToDriveBlock( HFS_ForkData &fork, int64 localBlock ) { // Search through the extents to find where this block is local int i ; for( i = 0; i < 8; i++ ) { if( localBlock < fork.extents[i].blockCount ) { return fork.extents[i].startBlock + localBlock; } else { localBlock -= fork.extents[i].blockCount; } } // Not found - could look in the extents overflow in the future return -1; } // HFS - Function to seek to a particular catalog node - return true if successful int HFS_JumpToCatalogNode( int DriveNum, int NodeNum ) { local int blockSize = drive[DriveNum].header.blockSize; local int nodeSize = drive[DriveNum].btree.headerNode.header.nodeSize; local int64 pos = (int64)nodeSize * NodeNum; local int64 localBlock = pos / blockSize; local int64 driveBlock = HFS_LocalToDriveBlock( drive[DriveNum].header.catalogFile, localBlock ); if( driveBlock < 0 ) { return false; } FSeek( drive[DriveNum].DriveStart + driveBlock*blockSize ); return true; } // HFS - Function to convert from a block to an address int64 HFS_BlockToAddress( int DriveNum, int64 block ) { return drive[DriveNum].DriveStart + block*drive[DriveNum].header.blockSize; } // HFS Catalog - stored as a btree typedef struct { local int DriveNum = parentof(this).DriveNum; // keep track of which drive this belongs to local int64 startPos = FTell(); // Define the header node HFS_BTHeaderNode headerNode; // Define the root node if( HFS_JumpToCatalogNode( DriveNum, headerNode.header.rootNode ) ) { HFS_BTNode rootNode; } } HFS_Catalog ; // Define an HFS drive typedef struct { local int DriveNum = NumDrives++; // keep track of the index of this drive local FILE_SYSTEM_TYPE FsType = FILE_SYSTEM_TYPE_HPFS; // local BYTE DriveBytesPerSectorShift = BytesPerSectorShift; // BUGBUG -- validate 4k sector support, then enable parameterization of NTFS_DRIVE local int64 DriveStart = FTell(); // Define the drive header BigEndian(); HFS_VolumeHeader header ; // Define the file catalog - stored as a btree FSeek( DriveStart + header.catalogFile.extents[0].startBlock * header.blockSize ); HFS_Catalog btree; // Define the root directory HFS_ListFiles( DriveNum, kHFSRootParentID ); LittleEndian(); } HFS_DRIVE ; //################################################################ // Unknown Drives //################################################################ // For unknown drive - define a union of known headers // and hope it is one of those typedef union (BYTE BytesPerSectorShift) { local int DriveNum = NumDrives++ ; // keep track of the index of this drive struct MASTER_BOOT_RECORD mbr; struct FAT_BOOTSECTOR boot_fat; struct NTFS_BOOTSECTOR boot_ntfs; struct IFS_BOOTSECTOR boot_ifs(BytesPerSectorShift); struct (BYTE param1) { struct EXFAT_BOOT_REGION boot_region(param1); struct EXFAT_BOOT_REGION boot_region(param1); } exfat (BytesPerSectorShift); } DRIVE_NOT_SUPPORTED ; //################################################################ // Detect drive type //################################################################ local const uint16 EXFAT_BOOT_SECTOR_SIGNATURE = 0xAA55; local const uint32 EXFAT_EXTENDED_BOOT_SECTOR_SIGNATURE = 0xAA550000; boolean fsrec_is_exfat_bootregion( quad startPosition, BYTE BytesPerSectorShift ) { local int i ; local DWORD bytesPerSector = BytesPerSectorShiftToBytesPerSector(BytesPerSectorShift); local quad tmpOffset; //Printf("FSREC-EXFAT: ENTER ... checking starting position %xh\r\n", startPosition); // 1. check for presence of "EXFAT " at offset 0x3 local char name[] = ReadString( startPosition + 3, 8); if (name != "EXFAT ") { //Printf("FSREC-EXFAT: string 'EXFAT ' not found at %xh\r\n", startPosition+3); return false; } // 2. check bytes 11..63 are all zero local char mustBeZero[53] ; ReadBytes(mustBeZero, startPosition + 11, 53); for (i = 0; i < 53; i++) { if (mustBeZero[i] != 0) { //Printf("FSREC-EXFAT: must-be-zero byte at offset %d was non-zero\r\n", i+11); return false; } } // 3. check BYTE at byte offset 108 matches BytesPerSectorShift local byte boot_bps = ReadByte(startPosition+108); if (boot_bps != BytesPerSectorShift) { //Printf("FSREC-EXFAT: byte at offset 108 (%d) does not match BytesPerSectorShift (%d)\r\n", boot_bps, BytesPerSectorShift); return false; } // 4. check boot sector signature at offset 510 local ushort boot_sector_signature = ReadUShort(startPosition+510); // this is always offset 510, even for 4k sectors if (boot_sector_signature != EXFAT_BOOT_SECTOR_SIGNATURE) { //Printf("FSREC-EXFAT: boot sector signature (%04xh) is not (%04xh)\r\n", boot_sector_signature, EXFAT_BOOT_SECTOR_SIGNATURE); return false; } // 5. check extended boot sector signatures local uint extended_boot_sector_signature; tmpOffset = startPosition + (bytesPerSector - 4); for (i = 1; i < 9; i++) { tmpOffset += bytesPerSector; extended_boot_sector_signature = ReadUInt(tmpOffset); if (extended_boot_sector_signature != 0xAA550000) { //Printf("FSREC-EXFAT: extended boot sector signature (%08xh) is not (%08xh) at position %xh\r\n", extended_boot_sector_signature, EXFAT_EXTENDED_BOOT_SECTOR_SIGNATURE, tmpOffset); return false; } } // that's enough to be fairly sure it's exFAT and same sector size... //Printf("FSREC-EXFAT: exFAT detected at %xh\r\n", startPosition); return true; } boolean fsrec_is_exfat( quad startPosition, BYTE BytesPerSectorShift ) { local int64 firstBootRegionStart = FTell(); local int64 secondBootRegionStart = FTell() + (12 * BytesPerSectorShiftToBytesPerSector(BytesPerSectorShift)); if (fsrec_is_exfat_bootregion(firstBootRegionStart, BytesPerSectorShift)) { return true; } if (fsrec_is_exfat_bootregion(secondBootRegionStart, BytesPerSectorShift)) { return true; } return false; } // Call with appropriate enumeration value to limit the types // of file systems checked for. Call with FILE_SYSTEM_TYPE_VALID_MASK // to detect any of the supported file systems. // Return value is a bit-wise combination of ALL the discovered // file system types. // // HACKHACK: I would use a struct with bitfield enums, but // local structs are not a supported feature as of v9.0. FILE_SYSTEM_TYPE FileSystemRecognizer(FILE_SYSTEM_TYPE allowedTypes, BYTE BytesPerSectorShift ) { if (allowedTypes == FILE_SYSTEM_TYPE_NONE) return FILE_SYSTEM_TYPE_NONE; // detect the file system type, using the current position // as the first sector of the volume (e.g., boot sector) local FILE_SYSTEM_TYPE result = FILE_SYSTEM_TYPE_NONE; local int64 startPos = FTell(); local DWORD bytesPerSector = BytesPerSectorShiftToBytesPerSector(BytesPerSectorShift); //Printf("FSREC Enter: startPos = %08x, allowedTypes == %s\r\n", startPos, ReadFILE_SYSTEM_TYPE(allowedTypes)); // todo: add fs recognition code FSeek(startPos); if (((BytesPerSectorShift == 9) || (BytesPerSectorShift == 12)) && (allowedTypes & FILE_SYSTEM_TYPE_EXFAT) && (fsrec_is_exfat(startPos, BytesPerSectorShift)) ) { result |= FILE_SYSTEM_TYPE_EXFAT; } FSeek(startPos); if ((BytesPerSectorShift == 9) && (allowedTypes & FILE_SYSTEM_TYPE_ANY_FAT_MASK)) { //Printf("FSREC : ? FAT_BOOTSECTOR\r\n"); FAT_BOOTSECTOR fat_boot ; if (IsValidFatBootSector(fat_boot)) { //Printf("FSREC : Valid: %s\r\n", ReadFILE_SYSTEM_TYPE(fat_boot.FsType)); result |= fat_boot.FsType; } else { //Printf("FSREC : FAT12/16/32 not valid\r\n"); } } FSeek(startPos); if ((BytesPerSectorShift == 9) && (allowedTypes & FILE_SYSTEM_TYPE_NTFS)) { //Printf("FSREC : ? NTFS_BOOTSECTOR\r\n"); NTFS_BOOTSECTOR ntfs_boot ; if (IsValidNtfsBootSector(ntfs_boot)) { //Printf("FSREC : Valid: NTFS\r\n"); result |= FILE_SYSTEM_TYPE_NTFS; } else { //Printf("FSREC : NTFS not valid\r\n"); } } FSeek(startPos); if ((BytesPerSectorShift == 9) && (allowedTypes & FILE_SYSTEM_TYPE_HPFS)) { //Printf("FSREC : ? HPFS\r\n"); if (IsValidHfsBootSector(FTell())) { //Printf("FSREC : Valid: HPFS shown as potentially valid\r\n"); result |= FILE_SYSTEM_TYPE_HPFS; } else { //Printf("FSREC : HPFS not valid\r\n"); } } FSeek(startPos); if (allowedTypes & FILE_SYSTEM_TYPE_IFS) { //Printf("FSREC : ? IFS_BOOTSECTOR\r\n"); IFS_BOOTSECTOR ifs_boot(BytesPerSectorShift) ; if (IsValidIfsBootsector(ifs_boot)){ //Printf("FSREC : Valid: IFS\r\n"); result |= FILE_SYSTEM_TYPE_IFS; } else { //Printf("FSREC : IFS not valid\r\n"); } } Printf("FSREC Exit : startPos = %08x, result == %s (%08xh) from allowed == %s\r\n", startPos, ReadFILE_SYSTEM_TYPE(result), result, ReadFILE_SYSTEM_TYPE(allowedTypes)); FSeek(startPos); return result; } // Map from PARTITION_SYSTEMID (which is a byte) to one or more preferred file system types // NOTE: uses int instead of PARTITION_SYSTEMID (which is a byte) // to allow callers to indicate 'unknown/undefined' by passing -1. FILE_SYSTEM_TYPE PreferredFileSystemTypeFromPartitionSystemID( int SystemID ) { local FILE_SYSTEM_TYPE preferredType = FILE_SYSTEM_TYPE_NONE; switch (SystemID) { // 05h, 0Fh, (15h?,) and C5h are extended partition, handled above case PARTITION_SYSTEMID_FAT_12: // 01h case PARTITION_SYSTEMID_FAT_12_HIDDEN: // 11h -- also FAT16 if supporting "logical sectored FAT" case PARTITION_SYSTEMID_FREE_FDISK_FAT12: // 8Dh case PARTITION_SYSTEMID_OLIVETTI: // AAh case PARTITION_SYSTEMID_SPEEDSTOR_LANSTEP_LINUX: // FEh preferredType |= FILE_SYSTEM_TYPE_FAT12; break; case PARTITION_SYSTEMID_FAT_16_INF32MB: // 04h case PARTITION_SYSTEMID_EXT_FAT16_INT13: // 0Eh case PARTITION_SYSTEMID_FAT_16_HIDDEN_INF32MB: // 14h -- also FAT12 if supporting "logical sectored FAT" case PARTITION_SYSTEMID_FAT_16_HIDDEN: // 16h case PARTITION_SYSTEMID_HIDDEN_FAT16_LBA: // 1Eh case 121: // 79h -- TODO: e.g., APTI alternative FAT16 case 122: // 7Ah -- TODO: e.g., APTI alternative FAT16 case 123: // 7Bh -- TODO: e.g., APTI alternative FAT16B case PARTITION_SYSTEMID_FREE_FDISK_PRIMARY_FAT16: // 90h case PARTITION_SYSTEMID_FREE_FDISK_LARGE_FAT16: // 92h case PARTITION_SYSTEMID_FREE_FDISK_FAT16_LBA: // 9Ah case PARTITION_SYSTEMID_DELL_POWEREDGE_UTIL: // DEh preferredType |= FILE_SYSTEM_TYPE_FAT16; break; case PARTITION_SYSTEMID_FAT_16: // 06h case PARTITION_SYSTEMID_OS2_HIDDEN_HIBERNATION: // 84h preferredType |= FILE_SYSTEM_TYPE_FAT12; preferredType |= FILE_SYSTEM_TYPE_FAT16; break; // These partition types were used by "logical sectored FAT" variants // Not having one of these images, I cannot test that this would work. // What is "logical sectored FAT"? a way to treat media as having // 1k, 2k, or 4k sector size when it's a normal 512 byte sector media. // Therefore, any disk image should be equal to one pulled from a // corresponding device with that sector size as a native sector size.... // // case PARTITION_SYSTEMID_AIX: // 08h -- FAT12/FAT16 // case PARTITION_SYSTEMID_FAT_12_HIDDEN: // 11h -- only FAT12 until tested, then add FAT16 // case PARTITION_SYSTEMID_FAT_16_HIDDEN_INF32MB: // 14h -- only FAT16 until tested, then add FAT12 // case PARTITION_SYSTEMID_NEC_DOS: // 24h -- FAT12/FAT16 // case PARTITION_SYSTEMID_GOLDEN_BOW_EZ_BIOS: // 56h -- FAT12/FAT16 // case 229: // E5h -- FAT12/FAT16 // case PARTITION_SYSTEMID_DOS_SECONDARY: // F2h -- FAT12/FAT16 case PARTITION_SYSTEMID_PRI_FAT32_INT13: // 0Bh case PARTITION_SYSTEMID_EXT_FAT32_INT13: // 0Ch case PARTITION_SYSTEMID_OSR2_FAT32: // 1Bh case PARTITION_SYSTEMID_OSR2_FAT32_LBA: // 1Ch case 124: // 7Ch -- TODO: e.g., APTI alternative FAT32 case 125: // 7Dh -- TODO: e.g., APTI alternative FAT32 case PARTITION_SYSTEMID_FREE_FDISK_FAT32: // 97h case PARTITION_SYSTEMID_CP_M: // DBh -- e.g., Dell system restore partition preferredType |= FILE_SYSTEM_TYPE_FAT32; break; case PARTITION_SYSTEMID_COMPAQ_DIAG: // 12h case PARTITION_SYSTEMID_FREE_FDISK_FAT32_LBA: // 98h -- Not just FAT32... case PARTITION_SYSTEMID_EFI_FS: // EFh -- EFI System partition, typically FAT32 preferredType |= FILE_SYSTEM_TYPE_ANY_FAT_MASK; preferredType |= FILE_SYSTEM_TYPE_EXFAT; break; case PARTITION_SYSTEMID_DDRDRIVE_SOLID_STATE_FS: // F7h -- e.g., O.S.G. by Natalia Portillo preferredType |= FILE_SYSTEM_TYPE_EXFAT; break; case PARTITION_SYSTEMID_NTFS_HPFS_EXFAT: // 07h case PARTITION_SYSTEMID_NTFS_HPFS_HIDDEN: // 17h preferredType |= FILE_SYSTEM_TYPE_NTFS; preferredType |= FILE_SYSTEM_TYPE_HPFS; preferredType |= FILE_SYSTEM_TYPE_EXFAT; break; case PARTITION_SYSTEMID_PQSERVICE_ROUTERBOOT: // 27h preferredType |= FILE_SYSTEM_TYPE_NTFS; break; case PARTITION_SYSTEMID_SHAGOS_SWAP_MACOS_X_HFS: // AFh preferredType |= FILE_SYSTEM_TYPE_HPFS; break; } return preferredType; } boolean DetectAndAddFileSystem( FILE_SYSTEM_TYPE preferredFilesSystems, BYTE BytesPerSectorShift ) { local int i ; local int64 startPos = FTell(); local int64 newStart ; local DWORD bytesPerSector = BytesPerSectorShiftToBytesPerSector(BytesPerSectorShift); local FILE_SYSTEM_TYPE detected = FILE_SYSTEM_TYPE_NONE; //Printf("ADD2() Enter: startPos = %08x, BytesPerSectorShift == %d, preferredFS == %s\r\n", startPos, BytesPerSectorShift, ReadFILE_SYSTEM_TYPE(preferredFilesSystems)); FSeek(startPos); // reset after calling FSRec() if (preferredFilesSystems != FILE_SYSTEM_TYPE_NONE) { // First look for a preferredType of file system detected = FileSystemRecognizer(preferredFilesSystems, BytesPerSectorShift); if (detected == FILE_SYSTEM_TYPE_NONE) { //Printf("ADD2() : Preferred type not detected, trying other types\r\n"); } } FSeek(startPos); // reset after calling FSRec() if (detected == FILE_SYSTEM_TYPE_NONE) { local FILE_SYSTEM_TYPE secondCheck = FILE_SYSTEM_TYPE_VALID_MASK ^ preferredFilesSystems; detected = FileSystemRecognizer(secondCheck, BytesPerSectorShift); // try all the other options... } FSeek(startPos); // reset after calling FSRec() if (detected == FILE_SYSTEM_TYPE_NONE) { //Printf("ADD2() : No drive due to no valid detected file system at offset %08Xh, %d\r\n", startPos, BytesPerSectorShift); return false; // not found } FSeek(startPos); // reset after calling FSRec() // assert that only one file system type was detected? Assert(IsExactlyOneBitSet(detected)); if (detected & FILE_SYSTEM_TYPE_EXFAT) { Printf("ADD2() : adding EXFAT\r\n"); EXFAT_DRIVE drive(BytesPerSectorShift); } else if (detected & FILE_SYSTEM_TYPE_FAT32) { Printf("ADD2() : adding FAT32\r\n"); FAT_DRIVE drive; } else if (detected & FILE_SYSTEM_TYPE_FAT16) { Printf("ADD2() : adding FAT16\r\n"); FAT_DRIVE drive; } else if (detected & FILE_SYSTEM_TYPE_FAT12) { Printf("ADD2() : adding FAT12\r\n"); FAT_DRIVE drive; } else if (detected & FILE_SYSTEM_TYPE_NTFS) { Printf("ADD2() : adding NTFS\r\n"); NTFS_DRIVE drive; } else if (detected & FILE_SYSTEM_TYPE_HPFS) { Printf("ADD2() : adding HPFS\r\n"); HFS_DRIVE drive; } else if (detected & FILE_SYSTEM_TYPE_IFS) { Printf("ADD2() : adding IFS\r\n"); IFS_BOOTSECTOR drive; } else { Printf("**** ADD2() : Code does not handle detected file system type: %s\r\n", EnumToString(detected)); Assert(!"Unsupported file system type?"); return false; } return true; } //////////////////////////////////////////////////////////////////////// // THIS IS THE ACTUAL STARTING POINT OF EXECUTION IN THE TEMPLATE //////////////////////////////////////////////////////////////////////// // Check for EndOfSectorMarker (present on MBR/FAT16/FAT32/NTFS) if( ReadUShort(510)!=0xAA55 && ReadUByte(1024) != 'H' ) { Warning( "File/Disk is not a valid MBR/FAT16/FAT32/NTFS/HFS. Template stopped." ); return -1; } // WHAT THE CODE PATH LOOKS LIKE TODAY: /////////////////////////////////////////// // 1. Detect partition-less file system by calling AutoDetectDrive(-1, 9) // 2. Detect partition-less file system by calling AutoDetectDrive(-1, 12) // 3. If either of the above worked, then end processing. // 4. Parse MBR to find all potential partitions (both 512 byte and 4k added), including container partitions // 5. Detect and add the file systems for each potential partition // SADLY: 010Editor does not support declaring 'local struct' instances, where it's just memory allocated for the struct. // Thus, a routine that parses the partition tables must simply add instances to three variables at a time. // THIS SUCKS, but can later be easily updated when local struct support is added, and is the "right" design... // I guess we'll just have a hard-coded maximum here... // local int qq_PotentialPartitionCount = 0; local quad qq_FilePosition [MAX_POTENTIAL_PARTITION_LOCATIONS] ; local int qq_SystemID [MAX_POTENTIAL_PARTITION_LOCATIONS] ; local BYTE qq_BytesPerSectorShift [MAX_POTENTIAL_PARTITION_LOCATIONS] ; local int ss_NotSupportedCount = 0; local quad ss_FilePosition [MAX_POTENTIAL_PARTITION_LOCATIONS] ; local int ss_SystemID [MAX_POTENTIAL_PARTITION_LOCATIONS] ; local BYTE ss_BytesPerSectorShift [MAX_POTENTIAL_PARTITION_LOCATIONS] ; boolean UnsupportedPartitionAlreadyExists(quad filePosition, int SystemID, BYTE BytesPerSectorShift) { local int t = 0; for (t = 0; t < ss_NotSupportedCount; t++) { if ((ss_FilePosition [t] == filePosition ) && (ss_SystemID [t] == SystemID ) && (ss_BytesPerSectorShift [t] == BytesPerSectorShift)) { return t; // non-zero, also the index } } return false; } void AddDriveNotSupportedLocation(quad filePosition, int SystemID, BYTE BytesPerSectorShift) { if (UnsupportedPartitionAlreadyExists(filePosition, SystemID, BytesPerSectorShift)) { return; } //Printf("Drive: DriveNotSupported at %08xh, systemID %02xh, bps_shift %d\r\n", filePosition, SystemID, BytesPerSectorShift); Assert(ss_NotSupportedCount < MAX_POTENTIAL_PARTITION_LOCATIONS); ss_FilePosition [ss_NotSupportedCount] = filePosition; ss_SystemID [ss_NotSupportedCount] = SystemID; ss_BytesPerSectorShift [ss_NotSupportedCount] = BytesPerSectorShift; ss_NotSupportedCount++; } boolean PotentialPartitionAlreadyExists(quad filePosition, int SystemID, BYTE BytesPerSectorShift) { local int t = 0; for (t = 0; t < qq_PotentialPartitionCount; t++) { if ((qq_FilePosition [t] == filePosition ) && (qq_SystemID [t] == SystemID ) && (qq_BytesPerSectorShift [t] == BytesPerSectorShift)) { return t; // non-zero, also the index } } return false; } void AddPotentialPartitionLocation(quad filePosition, int SystemID, BYTE BytesPerSectorShift) { //Printf("Found: Potential partition at %08xh, systemID %02xh, bps_shift %d\r\n", filePosition, SystemID, BytesPerSectorShift); Assert(qq_PotentialPartitionCount < MAX_POTENTIAL_PARTITION_LOCATIONS); qq_FilePosition [qq_PotentialPartitionCount] = filePosition; qq_SystemID [qq_PotentialPartitionCount] = SystemID; qq_BytesPerSectorShift [qq_PotentialPartitionCount] = BytesPerSectorShift; qq_PotentialPartitionCount++; } void ParsePartitionTableEntry(PARTITION_SYSTEMID SystemID, BYTE BytesPerSectorShift) { local int i ; local int64 startPos = FTell(); local int64 newStart ; local DWORD bytesPerSector = BytesPerSectorShiftToBytesPerSector(BytesPerSectorShift); //Printf("PPTE: Enter %08xh, SystemID %02xh, bps_shift %d\r\n", startPos, SystemID, BytesPerSectorShift); // Add container partitions as containers (even if not 100% sure they're correct), // and recursively call this function for each valid contained partition. if ( (SystemID == PARTITION_SYSTEMID_EXTENDED) || (SystemID == PARTITION_SYSTEMID_WIN95_EXT_PARTITION) || (SystemID == PARTITION_SYSTEMID_DRDOS_SECURED_EXTENDED) ) { if (BytesPerSectorShift != 9) { AddDriveNotSupportedLocation(startPos, SystemID, BytesPerSectorShift); return; } EXTENDED_PARTITION extended ; i = 0; while ( exists(extended.partitions[i]) ) { if(extended.partitions[i].SystemID != PARTITION_SYSTEMID_EMPTY) { newStart = startPos + extended.partitions[i].RelativeSector * bytesPerSector; if (!PotentialPartitionAlreadyExists(newStart, extended.partitions[i].SystemID, BytesPerSectorShift)) { FSeek( newStart ); ParsePartitionTableEntry(extended.partitions[i].SystemID, BytesPerSectorShift); } } i++; } return; } if (SystemID == PARTITION_SYSTEMID_LEGACY_MBR_EFI_HEADER) { // EEh EFI_PARTITION efi ; i = 0; while( exists( efi.partitions[i] ) ) { newStart = efi.partitions[i].FirstLBA * bytesPerSector; //Printf("PPTE: Adding GPT parition entry %d\r\n", i); AddPotentialPartitionLocation(newStart, -1, BytesPerSectorShift); i++; } return; } // if it's not a container, just add it as a potential partition AddPotentialPartitionLocation(startPos, -1, BytesPerSectorShift); return; } boolean ParseMBR() { local unsigned short i ; local unsigned short j ; local unsigned short mbr_boot_ok = 0; local quad tmpPartitionLocation; local uchar BootIndicator[4] = { 0x77, 0x77, 0x77, 0x77 }; local PARTITION_SYSTEMID SystemID[4]; //Printf("ParseMBR: Enter\r\n"); // Presume that there is an MBR for ( i=0; i<4; i++ ) { // Read the BootIndicator and SystemID values directly BootIndicator[i] = ReadUByte( 0x1BE + i*10h ); SystemID[i] = ReadUByte( 0x1C2 + i*10h ); //Printf("ParseMBR: [%d] BootIndicator %02xh SystemID %02x\r\n", i, BootIndicator[i], SystemID[i]); // only parse when: // a valid boot indicator (0x00 or 0x80) // AND the partition is *not* listed as an empty partition if( (BootIndicator[i] == SYSTEM_PARTITION || BootIndicator[i] == NOBOOT) && (SystemID[i] != PARTITION_SYSTEMID_EMPTY) ) { if ( mbr_boot_ok == 0 ) { //Printf("ParseMBR: [%d] Found a partition, adding MASTER_BOOT_RECORD\r\n", i); FSeek(0); MASTER_BOOT_RECORD boot_mbr ; mbr_boot_ok = 1; } // Parse the MBR partition for all supported sector sizes j = 0; while (exists(SupportedBytesPerSectorShift[j])) { //Printf("ParseMBR: [%d] Parsing partition table using sector size %d\r\n", i, SupportedBytesPerSectorShift[j]); tmpPartitionLocation = boot_mbr.partitions[i].RelativeSector; tmpPartitionLocation *= BytesPerSectorShiftToBytesPerSector(SupportedBytesPerSectorShift[j]); //Printf("ParseMBR: [%d] partition location calculated as %08xh\r\n", i, tmpPartitionLocation); FSeek(tmpPartitionLocation); if ( !PotentialPartitionAlreadyExists(tmpPartitionLocation, SystemID[i], SupportedBytesPerSectorShift[j]) ) { ParsePartitionTableEntry(SystemID[i], SupportedBytesPerSectorShift[j]); } j++; } } } //Printf("ParseMBR: exit (%d)\r\n", mbr_boot_ok); return mbr_boot_ok; } // Detect partition-free file system image Printf("\r\nDRIVE: Detecting partition-free file system image\r\n\r\n"); local int i ; while (exists(SupportedBytesPerSectorShift[i])) { if (DetectAndAddFileSystem(FILE_SYSTEM_TYPE_NONE, SupportedBytesPerSectorShift[i])) { Printf("DRIVE: Success with partition-free detection (sector size == %d).\r\n", BytesPerSectorShiftToBytesPerSector(SupportedBytesPerSectorShift[i])); return; } i++; } local int qq_j = 0; Printf("\r\nDRIVE: Attempting to parse the MBR\r\n\r\n"); if (!ParseMBR()) { Printf("DRIVE: Failed to parse MBR -- no supported file systems detected.\r\n"); qq_j = 0; while ( exists(SupportedBytesPerSectorShift[qq_j]) ) { FSeek(0); DRIVE_NOT_SUPPORTED drive(SupportedBytesPerSectorShift[qq_j]); qq_j++; } return; } // TODO: Sort potential partitions... because it's the right thing to do. Printf("\r\nDRIVE: found up to %d potential partitions...\r\n\r\n", qq_PotentialPartitionCount); local int qq_i = 0; local FILE_SYSTEM_TYPE preferred ; for (qq_i = 0; qq_i < qq_PotentialPartitionCount; qq_i++) { FSeek(qq_FilePosition[qq_i]); preferred = PreferredFileSystemTypeFromPartitionSystemID(qq_SystemID[qq_i]); if ((qq_SystemID[qq_i] == 237) || // EDh -- was proposed for GPT hybrid MBR solution, but no idea if used? (qq_SystemID[qq_i] == 21) || // 15h -- TODO: hidden extended partition (CHS) (qq_SystemID[qq_i] == 31) || // 1Fh -- TODO: hidden extended partition (LBA) (qq_SystemID[qq_i] == 133) || // 85h -- TODO: Linux extended partition (qq_SystemID[qq_i] == 145) || // 91h -- TODO: Free FDISK extended partition (CHS) (qq_SystemID[qq_i] == PARTITION_SYSTEMID_FREE_FDISK_EXTENDED_LBA) ) // 9Bh { // These partition types are containers of other partitions // but have no test case for them. AddDriveNotSupportedLocation(qq_FilePosition[qq_i], qq_SystemID[qq_i], qq_BytesPerSectorShift[qq_i]); } else if (!DetectAndAddFileSystem(preferred, qq_BytesPerSectorShift[qq_i])) { AddDriveNotSupportedLocation(qq_FilePosition[qq_i], qq_SystemID[qq_i], qq_BytesPerSectorShift[qq_i]); } } local int qq_k = 0; local int qq_found4k = 0; local int qq_found512 = 0; while ( exists(drive[qq_k]) ) { if (exists(drive[qq_k].DriveBytesPerSectorShift)) { if (drive[qq_k].DriveBytesPerSectorShift == 9) { qq_found512++; } else if (drive[qq_k].DriveBytesPerSectorShift == 12) { qq_found4k++; } } else { qq_found512++; } qq_k++; } local int qq_r = 0; for (qq_r = 0; qq_r < ss_NotSupportedCount; qq_r++) { FSeek( ss_FilePosition[qq_r] ); if ( ((qq_found4k > qq_found512) && (ss_BytesPerSectorShift[qq_r] == 12)) || ((qq_found4k < qq_found512) && (ss_BytesPerSectorShift[qq_r] == 9)) || (qq_found4k == qq_found512) ) { DRIVE_NOT_SUPPORTED drive( ss_BytesPerSectorShift[qq_r] ); } } return;