* @license http://creativecommons.org/licenses/by-sa/3.0/de/ Creative Commons Attribution-Share Alike 3.0 Germany * @copyright Copyright © 2010 tiggersWelt.net **/ class FAT { private $Image = null; private $SectorSize = 512; private $ReservedSectors = 1; private $ClusterSectors = 1; private $FATSectors = 0; private $FATCopies = 2; private $RootEntryCount = 0; private function freadint (&$f, $s, $p = 0, $t = true) { if (is_resource ($f)) { if ($p != 0) fseek ($f, $p, SEEK_SET); else $p = ftell ($f); $v = fread ($f, $s); if (!$t) fseek ($f, $p, SEEK_SET); } else { $v = substr ($f, $p, $s); if ($t) $f = ($p > 0 ? substr ($f, 0, $p) : '') . substr ($f, $p + $s); } $r = 0; for ($i = $s - 1; $i >= 0; $i--) $r = ($r << 8) + ord ($v [$i]); return $r; } function __construct ($Image, $Format = null, $SectorSize = null, $ReservedSectors = null, $ClusterSectors = null, $FATSectors = null, $FATCopies = null, $RootEntryCount = null) { // Try to open the image if (!is_resource ($this->Image = @fopen ($Image, 'rb'))) throw new Exception ('Could not open FAT'); // Read the whole file-system-information $buf = fread ($this->Image, 0x200); // Read all informations $Opcode = $this->freadint ($buf, 3); // 0x00 $OEMName = substr ($buf, 0, 8); // 0x03 $buf = substr ($buf, 8); $this->SectorSize = $this->freadint ($buf, 2); // 0x0B $this->ClusterSectors = $this->freadint ($buf, 1); // 0x0D $this->ReservedSectors = $this->freadint ($buf, 2); // 0x0E $this->FATCopies = $this->freadint ($buf, 1); // 0x10 $this->RootEntryCount = $this->freadint ($buf, 2); // 0x11 $Sectors = $this->freadint ($buf, 2); // 0x13 $Media = $this->freadint ($buf, 1); // 0x15 $this->FATSectors = $this->freadint ($buf, 2); // 0x16 $TrackSectors = $this->freadint ($buf, 2); // 0x18 $Pages = $this->freadint ($buf, 2); // 0x1A $HiddenSectors = $this->freadint ($buf, 4); // 0x1C $Sectors = $this->freadint ($buf, 4); // 0x20 // Read advanced information - this differs between FAT12/16 and FAT32 if (substr ($buf, 0x36 - 0x24, 3) == 'FAT') { $BIOSNumber = $this->freadint ($buf, 1); $this->freadint ($buf, 1); // Reserved byte $Signature = $this->freadint ($buf, 1); $FSID = $this->freadint ($buf, 4); $Name = substr ($buf, 0, 11); $Variant = substr ($buf, 11, 8); $Bootloader = substr ($buf, 19, 448); $buf = substr ($buf, 467); $BIOSBoot = $this->freadint ($buf, 2); $Flags = 0; $Version = 0; $RootCluster = 0; $FSInfoSector = 0; $BootCopySector = 0; } elseif (substr ($buf, 0x52 - 0x24, 3) == 'FAT') { $this->FATSectors = $this->freadint ($buf, 4); $Flags = $this->freadint ($buf, 2); $Version = $this->freadint ($buf, 2); $RootCluster = $this->freadint ($buf, 4); $FSInfoSector = $this->freadint ($buf, 2); $BootCopySector = $this->freadint ($buf, 2); $buf = substr ($buf, 12); // Reserved bytes $BIOSNumber = $this->freadint ($buf, 1); $this->freadint ($buf, 1); // Reserved byte $Signature = $this->freadint ($buf, 1); $FSID = $this->freadint ($buf, 4); $Name = substr ($buf, 0, 11); $Variant = substr ($buf, 11, 8); $Bootloader = substr ($buf, 19, 420); $buf = substr ($buf, 439); $BIOSBoot = $this->freadint ($buf, 2); } elseif ($Format === null) throw new Exception ('Unable to guess FAT-Format'); // Override by user-values if ($SectorSize !== null) $this->SectorSize = $SectorSize; if ($ClusterSectors !== null) $this->ClusterSectors = $ClusterSectors; if ($ReservedSectors !== null) $this->ReservedSectors = $ReservedSectors; if ($FATCopies !== null) $this->FATCopies = $FATCopies; if ($RootEntryCount !== null) $this->RootEntryCount = $RootEntryCount; if ($FATSectors !== null) $this->FATSectors = $FATSectors; } // {{{ getSectorSize /** * Retrive the size of a sector in bytes * * @access public * @return int **/ public function getSectorSize () { return $this->SectorSize; } // }}} // {{{ getReservedSectors /** * Retrive the number of reserved sectors on the image * * @access public * @return int **/ public function getReservedSectors () { return $this->ReservedSectors; } // }}} // {{{ getFATSize /** * Retrive the size of each FAT in bytes * * @access public * @return int **/ public function getFATSize () { return $this->getSectorSize () * $this->FATSectors; } // }}} // {{{ getFATCount /** * Retrive the number of FATs on this image * * @access public * @return int **/ public function getFATCount () { return $this->FATCopies; } // }}} // {{{ getFATPosition /** * Retrive the position of a FAT in the image * * @param int $Index (optional) Number of the FAT-Copy * * @access public * @return int **/ public function getFATPosition ($Index = 0) { if ($Index >= $this->getFATCount ()) return false; return $this->getSectorSize () * $this->getReservedSectors () + ($Index * $this->getFATSize ()); } // }}} // {{{ getRawFAT /** * Return the raw FAT-Segment from the image * * @param int $Index (optional) Number of the FAT-Copy * * @access public * @return string Raw data from FAT **/ public function getRawFAT ($Index = 0) { // Determine position of the FAT if (($p = $this->getFATPosition ($Index)) === false) return false; // Seek to that position fseek ($this->Image, $p, SEEK_SET); // Read and return the data return fread ($this->Image, $this->getFATSize ()); } // }}} // {{{ getRootPosition /** * Retrive the position of the Root-Index on the Image * * @access public * @return int **/ public function getRootPosition () { return $this->getFATPosition (0) + // Position of first FAT ($this->getFATSize () * $this->getFATCount ()); // Skip ahead all FATs } // }}} // {{{ getRootsize /** * Retrive the size of the root-index * * @access public * @return int **/ public function getRootSize () { return ($this->RootEntryCount * 32); } // }}} // {{{ parseDate /** * Parse a timestamp from FAT-Image * * @param int $Time * @param int $Date * * @access private * @return int **/ private function parseDate ($Time, $Date) { return mktime ( (($Time & 0xF800) >> 11), (($Time & 0x07E0) >> 5), ($Time & 0x001F) * 2, (($Date & 0x01E0) >> 5), ($Date & 0x001F), (($Date & 0xFE00) >> 9) + 1980 ); } // }}} // {{{ readIndex /** * Read a directory-index * * @access public * @return array **/ public function readIndex ($Position = null, $MaxSize = null) { // Auto-detect position of root-index if (($Position === null) && (($Position = $this->getRootPosition ()) === false)) return false; // Jump to the given position fseek ($this->Image, $Position, SEEK_SET); // Read the items $Read = 0; $rc = array (); while (($MaxSize === null) || ($Read < $MaxSize)) { $Read += 32; $Filename = fread ($this->Image, 8); // Skip removed file if (ord ($Filename [0]) == 229) { fread ($this->Image, 24); continue; } // End of index if (ord ($Filename [0]) == 0) break; $Fileext = fread ($this->Image, 3); $Attribs = $this->freadint ($this->Image, 1); $this->freadint ($this->Image, 1); // Reserved $CreatedMS = $this->freadint ($this->Image, 1); $CreateTime = $this->freadint ($this->Image, 2); // FAT-Time $CreateDate = $this->freadint ($this->Image, 2); // FAT-Date $AccessDate = $this->freadint ($this->Image, 2); // FAT-Date $UpperCluster = $this->freadint ($this->Image ,2); // FAT32 Only, FAT12/16 reserved $ModifyTime = $this->freadint ($this->Image, 2); // FAT-Time $ModifyDate = $this->freadint ($this->Image, 2); // FAT-Date $Cluster = $this->freadint ($this->Image, 2) + ($UpperCluster << 16); $Size = $this->freadint ($this->Image, 4); // Skip VFAT if ($Attribs == 0xF) continue; $rc [] = array ( 'Filename' => rtrim ($Filename) . '.' . $Fileext, 'Attributes' => $Attribs, 'Created' => $this->parseDate ($CreateTime, $CreateDate), 'Access' => $this->parseDate (0, $AccessDate), 'Modify' => $this->parseDate ($ModifyTime, $ModifyDate), 'Cluster' => $Cluster, 'Size' => $Size, ); } return $rc; } // }}} // {{{ getClusterSize /** * Retrive the size of a whole cluster * * @access public * @return int **/ public function getClusterSize () { return $this->getSectorSize () * $this->ClusterSectors; } // }}} // {{{ getDataPosition /** * Retrive the location of the data-segment * * @access public * @return int **/ public function getDataPosition () { return $this->getRootPosition () + $this->getRootSize (); } // }}} // {{{ readCluster /** * Read a single cluster from image * * @param int $Cluster Index of the cluster to read * * @access public * @return string **/ public function readCluster ($Cluster) { // Determine Size of a single cluster $clusterSize = $this->getClusterSize (); // Move to the right position fseek ($this->Image, $this->getDataPosition () + (($Cluster - 2) * $clusterSize), SEEK_SET); // Return the read return fread ($this->Image, $clusterSize); } // }}} // {{{ getNextCluster /** * Retrive the next cluster for a given Cluster * * @param int $Cluster Index of the cluster to find the next one for * * @access public * @return int Index of the next cluster or false if there is no next **/ public function getNextCluster ($Cluster, $FAT = 0) { // Retrive the position of the FAT $FAT = $this->getFATPosition ($FAT); // THIS IS FAT16 ONLY AT THE MOMENT!!! $FAT_Size = 2; // Move to the pointer to the right position fseek ($this->Image, $FAT + $Cluster * $FAT_Size, SEEK_SET); // Read the next candidate $Next = $this->freadint ($this->Image, $FAT_Size); // Check if there is no next cluster if (($Next == 0) || ($Next > 0xFFF6)) return false; // Return the next cluster return $Next; } // }}} // {{{ getClusters /** * Retrive a chain of clusters starting at a given position * * @param int $Cluster Index of the first cluster * * @access public * @return array **/ public function getClusters ($Cluster) { $rc = array ($Cluster); while ($Cluster = $this->getNextCluster ($Cluster)) $rc [] = $Cluster; return $rc; } // }}} // {{{ readFile /** * Read a file from a given cluster * * @param int $Cluster Index of the first cluster * @param int $Size (optional) Read this many bytes * * @access public * @return string **/ public function readFile ($Cluster, $Size = null) { // Detect all clusters to read $Clusters = $this->getClusters ($Cluster); // Auto-detect size if ($Size === null) $Size = count ($Clusters) * $this->getClusterSize (); // Read the file $buf = ''; foreach ($Clusters as $Cluster) { $buf .= $this->readCluster ($Cluster); if (strlen ($buf) >= $Size) break; } // Truncate data if neccessary if (strlen ($buf) > $Size) $buf = substr ($buf, 0, $Size); return $buf; } // }}} } ?>