//------------------------------------------------ //--- 010 Editor v7.0 Binary Template // // File: Drive.bt // Authors: SweetScape Software, Benjamin Vernoux // Version: 2.6 // Purpose: Parse logical and physical drives including // MBR, FAT12, FAT16, FAT32, 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: // 2.6 2018-11-14 HenryGab: Add support for FAT12. // 2.4 2018-11-13 HenryGab: Fix file system recognition sequence, support partition-less media // 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. //------------------------------------------------ LittleEndian(); local int NumDrives = 0; local const DWORD DefaultSectorSize = 512; local DWORD DetectedSectorSize = 0; // set to either 512 or 4096, as needed typedef enum { boolean_false = 0, boolean_true = 1, boolean_true_negative_one = -1, boolean_just_use_true_and_false = 0xbaadf00d } boolean; //################################################################ // 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 = 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 UBYTE GUID[16] ; string ReadGUID( GUID &g ) { local string s; SPrintf( s, "{%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X}", g[3],g[2],g[1],g[0],g[5],g[4],g[7],g[6],g[8],g[9],g[10],g[11],g[12],g[13],g[14],g[15] ); return s; } // 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 ) { string 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, EFI_MBR = 0x024DEE41, EFI_SYSTEM = 0xC12A7328, EFI_BIOS_BOOT = 0x21686148, EFI_IFFS = 0xD3BFE2DE, EFI_SONY_BOOT = 0xF4019732, EFI_LENOVO_BOOT = 0xBFBFAFE7, EFI_MSR = 0xE3C9E316, EFI_BASIC_DATA = 0xEBD0A0A2, EFI_LDM_META = 0x5808C8AA, EFI_LDM = 0xAF9B60A0, EFI_RECOVERY = 0xDE94BBA4, EFI_GPFS = 0x37AFFC90, EFI_STORAGE_SPACES = 0xE75CAF8F, EFI_HPUX_DATA = 0x75894C1E, EFI_HPUX_SERVICE = 0xE2A1E728, EFI_LINUX_DAYA = 0x0FC63DAF, EFI_LINUX_RAID = 0xA19D880F, EFI_LINUX_ROOT32 = 0x44479540, EFI_LINUX_ROOT64 = 0x4F68BCE3, EFI_LINUX_ROOT_ARM32 = 0x69DAD710, EFI_LINUX_ROOT_ARM64 = 0xB921B045, EFI_LINUX_SWAP = 0x0657FD6D, EFI_LINUX_LVM = 0xE6D6D379, EFI_LINUX_HOME = 0x933AC7E1, EFI_LINUX_SRV = 0x3B8F8425, EFI_LINUX_DM_CRYPT = 0x7FFEC5C9, EFI_LINUX_LUKS = 0xCA7D7CCB, EFI_LINUX_RESERVED = 0x8DA63339, EFI_FREEBSD_BOOT = 0x83BD6B9D, EFI_FREEBSD_DATA = 0x516E7CB4, EFI_FREEBSD_SWAP = 0x516E7CB5, EFI_FREEBSD_UFS = 0x516E7CB6, EFI_FREEBSD_VINUM = 0x516E7CB8, EFI_FREEBSD_ZFS = 0x516E7CBA, EFI_OSX_HFS = 0x48465300, EFI_OSX_UFS = 0x55465300, EFI_OSX_ZFS = 0x6A898CC3, EFI_OSX_RAID = 0x52414944, EFI_OSX_RAID_OFFLINE = 0x52414944, EFI_OSX_RECOVERY = 0x426F6F74, EFI_OSX_LABEL = 0x4C616265, EFI_OSX_TV_RECOVERY = 0x5265636F, EFI_OSX_CORE_STORAGE = 0x53746F72, EFI_SOLARIS_BOOT = 0x6A82CB45, EFI_SOLARIS_ROOT = 0x6A85CF4D, EFI_SOLARIS_SWAP = 0x6A87C46F, EFI_SOLARIS_BACKUP = 0x6A8B642B, EFI_SOLARIS_USR = 0x6A898CC3, EFI_SOLARIS_VAR = 0x6A8EF2E9, EFI_SOLARIS_HOME = 0x6A90BA39, EFI_SOLARIS_ALTERNATE = 0x6A9283A5, EFI_SOLARIS_RESERVED1 = 0x6A945A3B, EFI_SOLARIS_RESERVED2 = 0x6A9630D1, EFI_SOLARIS_RESERVED3 = 0x6A980767, EFI_SOLARIS_RESERVED4 = 0x6A96237F, EFI_SOLARIS_RESERVED5 = 0x6A8D2AC7, EFI_NETBSD_SWAP = 0x49F48D32, EFI_NETBSD_FFS = 0x49F48D5A, EFI_NETBSD_LFS = 0x49F48D82, EFI_NETBSD_RAID = 0x49F48DAA, EFI_NETBSD_CONCAT = 0x2DB519C4, EFI_NETBSD_ENCRYPT = 0x2DB519EC, EFI_CHROMEOS_KERNEL = 0xFE3A2A5D, EFI_CHROMEOS_ROOTFS = 0x3CB8E202, EFI_CHROMEOS_FUTURE = 0x2E0A753D, EFI_HAIKU = 0x42465331, EFI_MIDNIGHTBSD_BOOT = 0x85D5E45E, EFI_MIDNIGHTBSD_DATA = 0x85D5E45A, EFI_MIDNIGHTBSD_SWAP = 0x85D5E45B, EFI_MIDNIGHTBSD_UFS = 0x0394EF8B, EFI_MIDNIGHTBSD_VINUM = 0x85D5E45C, EFI_MIDNIGHTBSD_ZFS = 0x85D5E45D, EFI_CEPH_JOURNAL = 0x45B0969E, EFI_CEPH_ENCRYPT = 0x45B0969E, EFI_CEPH_OSD = 0x4FBD7E29, EFI_CEPH_ENCRYPT_OSD = 0x4FBD7E29, EFI_CEPH_CREATE = 0x89C57F98, EFI_CEPH_ENCRYPT_CREATE = 0x89C57F98, EFI_OPENBSD = 0x824CC7A0, EFI_QNX = 0xCEF5A9AD, EFI_PLAN9 = 0xC91818F9, EFI_VMWARE_VMKCORE = 0x9D275380, EFI_VMWARE_VMFS = 0xAA31E02A, EFI_VMWARE_RESERVED = 0x9198EFFC, } EFI_TYPE ; // EFI partition entry typedef struct { EFI_TYPE Type; // Use the first 4 bytes 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 local int size = parentof(this).header.PartitionSize; if( size > 128 ) UBYTE Unused[ size - 128 ] ; } EFI_PARTITION_ENTRY ; // 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 { // 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 local int count = 0; while( (ReadUQuad(FTell()+count*header.PartitionSize) != 0) && (count < header.NumPartitions) ) count++; // Create array of partitions if( count > 0 ) EFI_PARTITION_ENTRY partitions[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(FILE_SYSTEM_TYPE &type) { if (type == FILE_SYSTEM_TYPE_NONE) { return "FILE_SYSTEM_TYPE_NONE"; } local string 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"; } local uint32 invalidFlags = ((uint32)type) & FILE_SYSTEM_TYPE_INVALID_MASK; if (0 != invalidFlags) { local string tmp; SPrintf(tmp, "| 0x%x", type & invalidFlags); result += tmp; } result = SubStr(result, 2); return result; } //################################################################ // IFS Drives //################################################################ local const boolean IFS_VARIABLE_LENGTH_STRUCTURE = false; typedef struct IFS_BOOTSECTOR { // see FILE_SYSTEM_RECOGNITION_STRUCTURE from Microsoft docs 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; local UINT32 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] ; } }; // 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 = 0; local USHORT i = 0; local int64 start = startof(boot); local int64 end = start + boot.Length; // 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 the IFS structure goes beyond the end of file, then NO possible valid checksum if (end > FileSize()) { return 0xBAAD0002; } // Else calculate actual checksum 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; } //################################################################ // exFAT Drives //################################################################ typedef struct EXFAT_BOOTSECTOR { 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 ; // uint32 CountOfDataClusters ; // stored directly in the BPB, at last! uint32 RootCluster ; // first cluster, then follow FAT chain uint32 VolumeSerialNumber ; // all values are valid byte FileSystemRevisionMinor ; byte FileSystemRevisionMajor ; 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 ReservedBits : 13; 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 ; local FILE_SYSTEM_TYPE FsType = FILE_SYSTEM_TYPE_NONE; if (IsValidExfatBootSector(this)) { // and some fields whose sole purpose is simplify (duck-typing) usage by common FAT12/16/32 and exFAT code local uint16 BytesPerSector = 1 << BytesPerSectorShift; local byte SectorsPerCluster = 1 << SectorsPerClusterShift; local uint16 MaxRootDirEntries = 0; local uint32 MaximumValidClusterNumber = CountOfDataClusters + 1; FsType = FILE_SYSTEM_TYPE_EXFAT; // only set this after checked validity } }; boolean IsValidExfatBootSector(EXFAT_BOOTSECTOR &boot) { // EXFAT specifies quite a bit... if (boot.EndOfSectorMarker != 0xAA55) { return false; } // validate those MustBeZero bytes actually are zero local int i = 0; for (i = 0; i < 53; i++) { if (boot.MustBeZero[i] != 0) { return false; } } local uint32 minBytes = 1 << 20; // Minimum number of bytes in exFAT volume? if (boot.FileSystemRevisionMajor != 1) { Printf("INVALID exFAT (%08x): 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 (%08x): Bytes per sector shift value invalid (0x%02x)\r\n", startof(boot)); return false; } local uint32 bytesPerSector = 1 < boot.BytesPerSectorShift; local uint32 minimumSectors = minBytes >> boot.BytesPerSectorShift; if (minimumSectors > boot.NumberOfSectors) { Printf("INVALID exFAT (%08x): Volume too small (Min 0x%08x sectors, but only 0x%08x indicated)\r\n", startof(boot), minimumSectors, boot.NumberOfSectors); return false; } // Validate the checksum is repeated in sector[11] // This works even when using the backup copy of the boot sector, // as all math is relative to the startof(boot). // // Get the first 32-bit value from the sector local int64 offsetToChecksums = (int64)bytesPerSector * 11; local int64 checksumStart = startof(boot) + offsetToChecksums; local uint32 firstChecksum = ReadUInt(checksumStart); // verify it is repeated throughout the entire sector local uint32 currentChecksum; for (i = 0; i < bytesPerSector; i+=4) { currentChecksum = ReadUInt(checksumStart + i); if (currentChecksum != firstChecksum) { Printf("INVALID exFAT (%08x): Checksum not repeated for entire sector[11] ... (non-equal at offset 0x%x, value 0x%08x != 0x%08x)\r\n", startof(boot), i, currentChecksum, firstChecksum); return false; } } // this is mandatory... but I wonder if it's actually filled in as required? Assert(boot.OemName == "EXFAT "); return true; } //################################################################ // 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 ) { if (info == FAT12_CLUSTER_FREE_CLUSTER) { return "FREE_CLUSTER (000h)"; } else if (info == FAT12_CLUSTER_RESERVED_0001) { return "RESERVED (001h)"; } else if (info >= 0xFF0 && info <= 0xFF6) { local string s; SPrintf(s, "RESERVED (%03xh)", info); return s; } else if (info == FAT12_CLUSTER_BAD_CLUSTER) { return "BAD_CLUSTER (FF7h)"; } else if (info >= 0xFF8 && info <= 0xFFF) { local string s; SPrintf(s, "END_OF_CHAIN (%03xh)", info); return s; } else { local string s; 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 ) { if (info == FAT16_CLUSTER_FREE_CLUSTER) { return "FREE_CLUSTER (0000h)"; } else if (info == FAT16_CLUSTER_RESERVED_0001) { return "RESERVED (0001h)"; } else if (info >= 0xFFF0 && info <= 0xFFF6) { local string s; SPrintf(s, "RESERVED (%04xh)", info); return s; } else if (info == FAT16_CLUSTER_BAD_CLUSTER) { return "BAD_CLUSTER (FFF7h)"; } else if (info >= 0xFFF8 && info <= 0xFFFF) { local string s; SPrintf(s, "END_OF_CHAIN (%04xh)", info); return s; } else { local string s; 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 ulong tmp = info & 0x0FFFFFFF; ulong reserved_bits = info & 0xF0000000; 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 quad startFilePosition = FTell(); local DWORD count_of_data_clusters = CountOfDataClusters; // 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; // 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. FSeek( startFilePosition ); local DWORD i = CountOfDataClusters+2; 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) { local quad start = FTell(); // 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 to start, so FAT array indices correspond to stored values in the FAT chain FSeek( start ); FAT16_CLUSTER_INFO Cluster[ CountOfDataClusters + 2]; } FAT16_FAT_TABLE; typedef struct (DWORD CountOfDataClusters) { local quad start = FTell(); local DWORD count_of_data_clusters = CountOfDataClusters; // 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. FSeek( start ); local DWORD i = CountOfDataClusters+2; 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 { 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 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 local INT64 tmp = 510 - (FTell() - startof(this)); // 420 for FAT32, 448 for FAT16/12 UBYTE BootCode[tmp] ; WORD EndOfSectorMarker ; // Helpers to make FAT12/16/32 more common local DWORD ClusterSize = this.BytesPerSector * this.SectorsPerCluster; local DWORD NumberOfSectors = (this.NumberOfSectors16 != 0) ? this.NumberOfSectors16 : this.NumberOfSectors32; local DWORD SectorsPerFat = (this.SectorsPerFat16 != 0) ? this.SectorsPerFat16 : this.SectorsPerFat32; local DWORD CountOfDataClusters = ReadFAT_BOOTSECTOR_CountOfDataClusters(this); local DWORD MaximumValidClusterNumber = CountOfDataClusters + 1; // +2, but zero-based indexing, so largest valid value is Count+1 local FILE_SYSTEM_TYPE FsType = FILE_SYSTEM_TYPE_NONE; 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 ; // 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 (%08x): Unsupported file system type when calling IsFatTableSizeSufficient()\r\n", startof(boot)); return false; } // convert the number of bytes to number of sectors (rounding up) local DWORD required_sectors_per_fat ; 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 ) { if (boot.BytesPerSector == 0) return 0; 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 local DWORD rootDirectoryByteCount = boot.MaxRootDirEntries * 32; local DWORD rootDirectorySectors = rootDirectoryByteCount / boot.BytesPerSector; if (rootDirectoryByteCount % boot.BytesPerSector != 0) { rootDirectorySectors++; } // Next, the number of sectors in the data region local DWORD sectorsPerFat = boot.SectorsPerFat; local DWORD totalSectors = boot.NumberOfSectors; local DWORD dataSectors = totalSectors; dataSectors -= boot.ReservedSectors; dataSectors -= boot.NumberOfFatTables * sectorsPerFat; dataSectors -= rootDirectorySectors; local DWORD countOfClusters = dataSectors / boot.SectorsPerCluster; // note that this rounds down return countOfClusters; } boolean IsValidFatBootSector(FAT_BOOTSECTOR &boot) { // FAT12/16/32 don't specify quite as much as mandatory... if (boot.EndOfSectorMarker != 0xAA55) { Printf("INVALID FAT (%08x): EndOfSectorMark not 0xAA55 (0x55 0xAA)\r\n", startof(boot)); return false; } if ((boot.FsType != FILE_SYSTEM_TYPE_FAT32) && (boot.FsType != FILE_SYSTEM_TYPE_FAT16) && (boot.FsType != FILE_SYSTEM_TYPE_FAT12) ) { Printf("INVALID FAT (%08x): 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 (%08x): 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 (%08x): SectorsPerCluster (%x)\r\n", startof(boot), boot.SectorsPerCluster); return false; } if (boot.ReservedSectors == 0) { Printf("INVALID FAT (%08x): ReservedSectors is zero\r\n", startof(boot)); return false; } if ((boot.NumberOfFatTables != 1) && (boot.NumberOfFatTables != 2)) { Printf("INVALID FAT (%08x): 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 (%08x): MaxRootDirEntries must be zero (%x)\r\n", startof(boot), boot.MaxRootDirEntries); return false; } if (boot.NumberOfSectors16 != 0) { Printf("INVALID FAT32 (%08x): NumberOfSectors16 must be zero (%x)\r\n", startof(boot), boot.NumberOfSectors16); return false; } if (boot.NumberOfSectors32 == 0) { Printf("INVALID FAT32 (%08x): NumberOfSectors32 cannot be zero\r\n", startof(boot)); return false; } if (boot.SectorsPerFat16 != 0) { Printf("INVALID FAT32 (%08x): SectorsPerFat16 must be zero (%x)\r\n", startof(boot), boot.SectorsPerFat16); return false; } if (boot.SectorsPerFat32 == 0) { Printf("INVALID FAT32 (%08x): SectorsPerFat32 cannot be zero\r\n", startof(boot)); return false; } if (boot.RootCluster == 0) { Printf("INVALID FAT32 (%08x): 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 (%08x): MaxRootDirEntries cannot be zero\r\n", startof(boot)); return false; } if ((boot.NumberOfSectors16 == 0) && (boot.NumberOfSectors32 == 0)) { Printf("INVALID FAT12/16 (%08x): NumberOfSectors16 and NumberOfSectors32 cannot both be zero\r\n", startof(boot)); return false; } if (boot.SectorsPerFat16 == 0) { Printf("INVALID FAT12/16 (%08x): SectorsPerFat16 cannot be zero (%x)\r\n", startof(boot)); return false; } } local DWORD required_sectors_per_fat ; required_sectors_per_fat = ReadFAT_BOOTSECTOR_SectorsRequiredForDataClusters(boot); if (boot.SectorsPerFat < required_sectors_per_fat) { Printf("INVALID FAT (%08x): 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 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 ; // FAT Boot sector FAT_BOOTSECTOR boot_sector ; if (!IsValidFatBootSector(boot_sector)) { Printf("INVALID FAT (%08x): Drive creation attempted when boot sector invalid?", startof(boot_sector)); return; } local quad BytesPerSector = boot_sector.BytesPerSector; local quad 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 local FILE_SYSTEM_TYPE FsType = boot_sector.FsType; // Add the FAT structures local quad pos; 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; if (tmp2) { 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 ) { if (f.Name[0]==0) { return ""; // indicator of last directory entry } local int nameLength = 8; local int extensionLength = 3; while (nameLength > 0 && f.Name [ nameLength - 1 ] == ' ') { nameLength -= 1; } while (extensionLength > 0 && f.Extension[ extensionLength - 1 ] == ' ') { extensionLength -= 1; } local string name; local string extension; 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 local int addPeriod = extensionLength > 0; if (f.Attribute == FAT_ATTR_LongFileNameEntry) { addPeriod = false; } else if (f.Attribute & FAT_ATTR_VolumeId) { addPeriod = false; } local string 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 ) { string s; if(f.Name[0]==0) { return "Last Dir Entry (Empty)"; } s = ReadFAT_SHORTENTRY_Filename(f); if( (uchar)f.Name[0] == 0xE5 ) // must goto structure, not 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)*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 DefaultSectorSize; } // 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 DefaultSectorSize; } //################################################################ // 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 ) { string s; if( flags.Directory ) s += "Directory "; if( flags.ReadOnly ) s += "ReadOnly "; if( flags.Hidden ) s += "Hidden "; if( flags.System ) s += "System "; return s; } // 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 ) { string s; if( exists( fn.fileNameAttr ) ) s = fn.fileNameAttr.FileName; if( fn.header.HasSubNode ) { 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(); 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 ); local int i; 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(); 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 ) wchar_t Name[NameLength]; } header; if( !header.NonResident ) { // Resident attributes 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 ) { // File data is stored directly in the header for small files if( header.AttributeLength > 0 ) ubyte fileData[ header.AttributeLength ]; } } else { // Non-resident attribute - data is in a different place in the // drive and use runlist to locate NTFS_RUN_LIST runList( start + header.Length ); if( (header.Type == INDEX_ALLOCATION) && (parentof(this).header.IsDirectory) ) { // Stores an index (btree) of file names local int i; 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; 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; local int dataAttr = NTFS_FindAttribute( drive[DriveNum].mft.mft[0], DATA ); local int fileRecsPerCluster = drive[DriveNum].ClusterSize / 1024; local UQUAD 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(); // 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; UBYTE EndTag[2]; UBYTE FixupArray[6]; } header; // Check header if( header.Magic != "FILE" ) return; // Read list of attributes 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 local int 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 ) { string name; local int attrNum = NTFS_FindAttribute( fr, FILE_NAME ); 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 local int hidden = fr.attribute[attrNum].fileName.Flags.Hidden; local int 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 ) { if( exists( mft.mft[3] ) ) { local int 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 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 + DefaultSectorSize ); 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; 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 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 { 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; } DRIVE_NOT_SUPPORTED; //################################################################ // Detect drive type //################################################################ // 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) { // 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(); Printf("FSREC Enter: startPos = %08x, allowedTypes == %s\r\n", startPos, ReadFILE_SYSTEM_TYPE(allowedTypes)); // todo: add fs recognition code if (allowedTypes & FILE_SYSTEM_TYPE_EXFAT) { FSeek(startPos); Printf("FSREC : ? EXFAT_BOOTSECTOR\r\n"); EXFAT_BOOTSECTOR exfat_boot ; if (IsValidExfatBootSector(exfat_boot)) { Printf("FSREC : Valid: exFAT\r\n"); result |= FILE_SYSTEM_TYPE_EXFAT; } else { Printf("FSREC : exFAT not valid\r\n"); } } if (allowedTypes & FILE_SYSTEM_TYPE_ANY_FAT_MASK) { FSeek(startPos); 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"); } } if (allowedTypes & FILE_SYSTEM_TYPE_NTFS) { FSeek(startPos); 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"); } } if (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"); } } if (allowedTypes & FILE_SYSTEM_TYPE_IFS) { FSeek(startPos); Printf("FSREC : ? IFS_BOOTSECTOR\r\n"); IFS_BOOTSECTOR ifs_boot ; 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\r\n", startPos, ReadFILE_SYSTEM_TYPE(result)); 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. // // 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: // 07h case PARTITION_SYSTEMID_NTFS_HPFS_HIDDEN: // 17h preferredType |= FILE_SYSTEM_TYPE_NTFS; preferredType |= FILE_SYSTEM_TYPE_HPFS; preferredType |= FILE_SYSTEM_TYPE_EXFAT; // These partition types are also used for 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; } // Used to prevent drives from being defined twice - this could // happen if a drive is listed in the MBR and the EFI table int DriveAlreadyExists( int64 pos ) { local int i; for( i = 0; i < NumDrives; i++ ) { if( startof(drive[i]) == pos ) return true; } return false; } // Function to detect different types of drives // NOTE: uses int instead of PARTITION_SYSTEMID (which is a byte) // to allow callers to indicate 'unknown/undefined' by passing -1. int AutoDetectDrive( int SystemID ) { local int i ; local int64 startPos = FTell(); local string SystemIDAsString = ReadPARTITION_SYSTEMID(SystemID); local int64 newStart; Printf("ADD() Enter: startPos = %08x, SystemID == %s\r\n", startPos, SystemIDAsString); if ( SystemID == 237 ) { // EDh -- was proposed for GPT hybrid MBR solution, but no idea if used? Printf("ADD() Exit: startPos = %08x, GPT hybrid MBR unsupported\r\n", startPos); DRIVE_NOT_SUPPORTED drive; return; } // TODO -- Fill in the below sections marked as "unsupported"? // a number of partition types might initially appear valid, // but could case script to crash. Feel free to remove those checks, // to allow running FS recognizer on them anyways. // Handle partitions that are themselves container partitions if ( (SystemID == 21) || // 15h -- TODO: hidden extended partition (CHS) (SystemID == 31) || // 1Fh -- TODO: hidden extended partition (LBA) (SystemID == 133) || // 85h -- TODO: Linux extended partition (SystemID == 145) || // 91h -- TODO: Free FDISK extended partition (CHS) (SystemID == PARTITION_SYSTEMID_FREE_FDISK_EXTENDED_LBA) ) // 9Bh { // These partition types are containers of other partitions // but have no test case for them. Printf("ADD() Exit: startPos = %08x, partition type %s unsupported\r\n", startPos, SystemIDAsString); DRIVE_NOT_SUPPORTED drive; return; } if ( (SystemID == PARTITION_SYSTEMID_EXTENDED) || (SystemID == PARTITION_SYSTEMID_WIN95_EXT_PARTITION) || (SystemID == PARTITION_SYSTEMID_DRDOS_SECURED_EXTENDED) ) { // Extended partition EXTENDED_PARTITION extended ; // Find the extended drives for( i = 0; i < 2; i++ ) { if( extended.partitions[i].SystemID != EMPTY ) { // BUGBUG-4kn -- magic required here to support 4k sectors newStart = startPos + extended.partitions[i].RelativeSector*(INT64)DefaultSectorSize; Printf("ADD() : checking extended[%d] (%x)\r\n", i, newStart); FSeek( newStart ); if( !DriveAlreadyExists(FTell()) ) { if( !AutoDetectDrive( extended.partitions[i].SystemID ) ) { DRIVE_NOT_SUPPORTED drive; } } } } Printf("ADD() Exit: startPos = %08x, extended partitions\r\n", startPos); return true; } if (SystemID == PARTITION_SYSTEMID_LEGACY_MBR_EFI_HEADER) // EEh { // EFI partition EFI_PARTITION efi ; // Find the drives i = 0; while( exists( efi.partitions[i] ) ) { // BUGBUG-4kn -- magic required here to support 4k sectors newStart = efi.partitions[i].FirstLBA * DefaultSectorSize; Printf("ADD() : checking EFI[%d] (%x)\r\n", i, newStart); FSeek( newStart ); if( !AutoDetectDrive( -1 ) ) { DRIVE_NOT_SUPPORTED drive; } i++; } Printf("ADD() Exit: startPos = %08x, EFI partitions\r\n", startPos); return true; } local FILE_SYSTEM_TYPE preferredType = PreferredFileSystemTypeFromPartitionSystemID(SystemID); // Now loop twice... first for a preferredType of file system // then for any other file system local FILE_SYSTEM_TYPE detected = FileSystemRecognizer(preferredType); FSeek(startPos); // reset after calling FSRec() if (detected == FILE_SYSTEM_TYPE_NONE) { Printf("ADD() : Preferred type not detected, trying other types\r\n"); local FILE_SYSTEM_TYPE secondCheck = FILE_SYSTEM_TYPE_VALID_MASK ^ preferredType; detected = FileSystemRecognizer(secondCheck); // try all the other options... FSeek(startPos); // reset after calling FSRec() } Printf("ADD() : detected %s\r\n", EnumToString(detected)); if (detected & FILE_SYSTEM_TYPE_EXFAT) { Printf("ADD() : adding EXFAT\r\n"); EXFAT_BOOTSECTOR boot; return true; } else if (detected & FILE_SYSTEM_TYPE_FAT32) { Printf("ADD() : adding FAT32\r\n"); FAT_DRIVE drive; return true; } else if (detected & FILE_SYSTEM_TYPE_FAT16) { Printf("ADD() : adding FAT16\r\n"); FAT_DRIVE drive; return true; } else if (detected & FILE_SYSTEM_TYPE_FAT12) { Printf("ADD() : adding FAT12\r\n"); FAT_DRIVE drive; return true; } else if (detected & FILE_SYSTEM_TYPE_NTFS) { Printf("ADD() : adding NTFS\r\n"); NTFS_DRIVE drive; return true; } else if (detected & FILE_SYSTEM_TYPE_HPFS) { Printf("ADD() : adding HPFS\r\n"); HFS_DRIVE drive; return true; } else if (detected & FILE_SYSTEM_TYPE_IFS) { Printf("ADD() : adding IFS\r\n"); IFS_BOOTSECTOR boot; return true; } else if (detected == FILE_SYSTEM_TYPE_NONE) { Printf("ADD() : No drive due to no valid detected file system at offset 0x%08x\r\n", startPos); return false; // not found } Assert(!"Unsupported file system type?"); return false; } //////////////////////////////////////////////////////////////////////// // 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; } // Check for different drive types local boolean AutoDetectDriveResult = AutoDetectDrive(-1); if (!AutoDetectDriveResult) { // Auto detect the MBR // BUGBUG-4kn -- If MBR, how to detect 4k sector size? // Why? how else to calculate proper offset for partition? local unsigned short mbr_boot_ok=0; local unsigned short i; local PARTITION_SYSTEMID SystemID[4]; local uchar BootIndicator[4] = { 0x77, 0x77, 0x77, 0x77 }; local boolean SystemID_AutoDetectDrive_Result[4] = { 0x777, 0x777, 0x777, 0x777 }; local FILE_SYSTEM_TYPE PreferredFS[4] = { FILE_SYSTEM_TYPE_NONE, FILE_SYSTEM_TYPE_NONE, FILE_SYSTEM_TYPE_NONE, FILE_SYSTEM_TYPE_NONE }; for ( i=0; i<4; i++ ) { // Check BootIndicator and SystemID BootIndicator[i] = ReadUByte( 0x1BE + i*10h ); SystemID[i] = ReadUByte( 0x1C2 + i*10h ); // only parse when: // BOTH a valid boot indicator (0x00 or 0x80) // AND when the partition is not listed as an empty partition if( (BootIndicator[i] == SYSTEM_PARTITION || BootIndicator[i] == NOBOOT) && (SystemID[i] != PARTITION_SYSTEMID_EMPTY) ) { // If the above holds true, then we presume this is an MBR-based media // and thus initialize the MBR structure if not already having done so. if ( mbr_boot_ok == 0 ) { FSeek(0); MASTER_BOOT_RECORD boot_mbr ; mbr_boot_ok = 1; } // Jump to Partition using boot_mbr structure definition of partition // BUGBUG-4kn -- magic required here to support 4k sectors FSeek(boot_mbr.partitions[i].RelativeSector*(INT64)DefaultSectorSize); // If the drive already exists (e.g., MBR+GPT both listing partitions) // then do not add the drive again if( !DriveAlreadyExists(FTell()) ) { PreferredFS[i] = PreferredFileSystemTypeFromPartitionSystemID(SystemID[i]); SystemID_AutoDetectDrive_Result[i] = AutoDetectDrive(SystemID[i]); if(!SystemID_AutoDetectDrive_Result[i]) { DRIVE_NOT_SUPPORTED drive; } } } } // Could not find MBR - drive is not supported if( !mbr_boot_ok ) { DRIVE_NOT_SUPPORTED drive; } }