diff --git a/src/Cli/IsoTool.php b/src/Cli/IsoTool.php index 842ed4e..a7580ea 100644 --- a/src/Cli/IsoTool.php +++ b/src/Cli/IsoTool.php @@ -122,9 +122,9 @@ protected function extractAction(string $file, string $extractPath): void echo 'Extract finished!' . PHP_EOL; } - protected function extractFiles(Volume $primaryVolume, IsoFile $isoFile, string $destinationDir): void + protected function extractFiles(Volume $volumeDescriptor, IsoFile $isoFile, string $destinationDir): void { - $pathTable = $primaryVolume->loadTable($isoFile); + $pathTable = $volumeDescriptor->loadTable($isoFile); if ($pathTable === null) { return; @@ -135,7 +135,7 @@ protected function extractFiles(Volume $primaryVolume, IsoFile $isoFile, string /** @var PathTableRecord $pathRecord */ foreach ($pathTable as $pathRecord) { // check extents - $extents = $pathRecord->loadExtents($isoFile, $primaryVolume->blockSize); + $extents = $pathRecord->loadExtents($isoFile, $volumeDescriptor->blockSize, ($volumeDescriptor->getType() === Type::SUPPLEMENTARY_VOLUME_DESC), $volumeDescriptor->jolietLevel); if ($extents !== false) { /** @var FileDirectory $extentRecord */ @@ -153,7 +153,7 @@ protected function extractFiles(Volume $primaryVolume, IsoFile $isoFile, string $dataLength = $extentRecord->dataLength; echo $fullPath . ' (location: ' . $location . ') (length: ' . $dataLength . ')' . PHP_EOL; - $pathRecord->extractFile($isoFile, $primaryVolume->blockSize, $location, $dataLength, $fullPath); + $pathRecord->extractFile($isoFile, $volumeDescriptor->blockSize, $location, $dataLength, $fullPath); } else { if (! is_dir($fullPath)) { if (mkdir($fullPath) === false) { @@ -172,6 +172,7 @@ protected function infoVolume(Volume $volumeDescriptor): void echo ' - System ID: ' . $volumeDescriptor->systemId . PHP_EOL; echo ' - Volume ID: ' . $volumeDescriptor->volumeId . PHP_EOL; echo ' - App ID: ' . $volumeDescriptor->appId . PHP_EOL; + echo ' - File Structure Version: ' . $volumeDescriptor->fileStructureVersion . PHP_EOL; echo ' - Volume Space Size: ' . $volumeDescriptor->volumeSpaceSize . PHP_EOL; echo ' - Volume Set Size: ' . $volumeDescriptor->volumeSetSize . PHP_EOL; echo ' - Volume SeqNum: ' . $volumeDescriptor->volumeSeqNum . PHP_EOL; @@ -186,6 +187,10 @@ protected function infoVolume(Volume $volumeDescriptor): void echo ' - Modification Date: ' . $volumeDescriptor->modificationDate?->toDateTimeString() . PHP_EOL; echo ' - Expiration Date: ' . $volumeDescriptor->expirationDate?->toDateTimeString() . PHP_EOL; echo ' - Effective Date: ' . $volumeDescriptor->effectiveDate?->toDateTimeString() . PHP_EOL; + + if ($volumeDescriptor instanceof SupplementaryVolume && $volumeDescriptor->jolietLevel !== 0) { + echo ' - Joliet Level: ' . $volumeDescriptor->jolietLevel . PHP_EOL; + } } protected function displayFiles(Volume $volumeDescriptor, IsoFile $isoFile): void @@ -201,7 +206,7 @@ protected function displayFiles(Volume $volumeDescriptor, IsoFile $isoFile): voi /** @var PathTableRecord $pathRecord */ foreach ($pathTable as $pathRecord) { // check extents - $extents = $pathRecord->loadExtents($isoFile, $volumeDescriptor->blockSize); + $extents = $pathRecord->loadExtents($isoFile, $volumeDescriptor->blockSize, ($volumeDescriptor->getType() === Type::SUPPLEMENTARY_VOLUME_DESC), $volumeDescriptor->jolietLevel); if ($extents !== false) { /** @var FileDirectory $extentRecord */ diff --git a/src/Descriptor/Volume.php b/src/Descriptor/Volume.php index f523e1e..2a486c6 100644 --- a/src/Descriptor/Volume.php +++ b/src/Descriptor/Volume.php @@ -38,6 +38,7 @@ abstract class Volume extends Descriptor public ?Carbon $expirationDate = null; public ?Carbon $effectiveDate = null; public int $fileStructureVersion; + public int $jolietLevel = 0; public function init(IsoFile $isoFile, int &$offset): void { @@ -58,8 +59,26 @@ public function init(IsoFile $isoFile, int &$offset): void $this->volumeSpaceSize = Buffer::readBBO($this->bytes, 8, $offset); - // unused - $unused = Buffer::getBytes($this->bytes, 32, $offset); + // joliet escape sequence + $jolietEscapeSequence = Buffer::getBytes($this->bytes, 32, $offset); + + // Joliet Detection - If this is a Supplementary Volume Descriptor + if ($this->type === Type::SUPPLEMENTARY_VOLUME_DESC) { + // Define Joliet escape sequences as their byte values + $jolietLevels = [ + 1 => '374764', // %/@ in byte format + 2 => '374767', // %/C in byte format + 3 => '374769', // %/E in byte format + ]; + + // Check for Joliet escape sequences indicating Unicode support + foreach ($jolietLevels as $level => $sequence) { + if (str_contains($jolietEscapeSequence, $sequence)) { + $this->jolietLevel = $level; // Record the Joliet level if found + break; + } + } + } $this->volumeSetSize = Buffer::readBBO($this->bytes, 4, $offset); $this->volumeSeqNum = Buffer::readBBO($this->bytes, 4, $offset); @@ -72,6 +91,7 @@ public function init(IsoFile $isoFile, int &$offset): void $this->optMPathTablePos = Buffer::readMSB($this->bytes, 4, $offset); $this->rootDirectory = new FileDirectory(); + $this->rootDirectory->jolietLevel = $this->jolietLevel; $this->rootDirectory->init($this->bytes, $offset, ($this->type === Type::SUPPLEMENTARY_VOLUME_DESC)); $this->volumeSetId = trim(Buffer::readDString($this->bytes, 128, $offset, ($this->type === Type::SUPPLEMENTARY_VOLUME_DESC))); diff --git a/src/FileDirectory.php b/src/FileDirectory.php index 788a621..ec5e927 100644 --- a/src/FileDirectory.php +++ b/src/FileDirectory.php @@ -107,6 +107,8 @@ class FileDirectory */ public string $fileId; + public int $jolietLevel = 0; + /** * Load the "Directory Record" from buffer * @@ -149,11 +151,20 @@ public function init(array &$buffer, int &$offset, bool $supplementary = false): $this->fileId = '..'; $tmp++; } else { - $this->fileId = Buffer::readDString($buffer, $this->fileIdLength, $tmp, $supplementary); - - $pos = strpos($this->fileId, ';1'); - if ($pos !== false && $pos === strlen($this->fileId) - 2) { - $this->fileId = substr($this->fileId, 0, strlen($this->fileId) - 2); + if ($this->jolietLevel === 3) { + $this->fileId = Buffer::readAString($buffer, $this->fileIdLength, $tmp, $supplementary); + + $pos = strpos($this->fileId, ';1'); + if ($pos !== false && $pos === strlen($this->fileId) - 2) { + $this->fileId = substr($this->fileId, 0, strlen($this->fileId) - 2); + } + } else { + $this->fileId = Buffer::readDString($buffer, $this->fileIdLength, $tmp, $supplementary); + + $pos = strpos($this->fileId, ';1'); + if ($pos !== false && $pos === strlen($this->fileId) - 2) { + $this->fileId = substr($this->fileId, 0, strlen($this->fileId) - 2); + } } } @@ -238,9 +249,9 @@ public function isParent(): bool * * @return array|false */ - public function loadExtents(IsoFile $isoFile, int $blockSize, bool $supplementary = false): array|false + public function loadExtents(IsoFile $isoFile, int $blockSize, bool $supplementary = false, int $jolietLevel = 0): array|false { - return self::loadExtentsSt($isoFile, $blockSize, $this->location, $supplementary); + return self::loadExtentsSt($isoFile, $blockSize, $this->location, $supplementary, $jolietLevel); } /** @@ -248,7 +259,7 @@ public function loadExtents(IsoFile $isoFile, int $blockSize, bool $supplementar * * @return array|false */ - public static function loadExtentsSt(IsoFile $isoFile, int $blockSize, int $location, bool $supplementary = false): array|false + public static function loadExtentsSt(IsoFile $isoFile, int $blockSize, int $location, bool $supplementary = false, int $jolietLevel = 0): array|false { if ($isoFile->seek($location * $blockSize, SEEK_SET) === -1) { return false; @@ -268,11 +279,13 @@ public static function loadExtentsSt(IsoFile $isoFile, int $blockSize, int $loca $offset = 1; $fdDesc = new self(); + $fdDesc->jolietLevel = $jolietLevel; $extents = []; while ($fdDesc->init($bytes, $offset, $supplementary) !== false) { $extents[] = $fdDesc; $fdDesc = new self(); + $fdDesc->jolietLevel = $jolietLevel; } return $extents; diff --git a/src/PathTableRecord.php b/src/PathTableRecord.php index 0c20618..b56b6d3 100644 --- a/src/PathTableRecord.php +++ b/src/PathTableRecord.php @@ -81,9 +81,9 @@ public function init(array &$bytes, int &$offset, bool $supplementary = false): * * @return array|false */ - public function loadExtents(IsoFile &$isoFile, int $blockSize, bool $supplementary = false): array|false + public function loadExtents(IsoFile &$isoFile, int $blockSize, bool $supplementary = false, int $jolietLevel = 0): array|false { - return FileDirectory::loadExtentsSt($isoFile, $blockSize, $this->location, $supplementary); + return FileDirectory::loadExtentsSt($isoFile, $blockSize, $this->location, $supplementary, $jolietLevel); } public function extractFile(IsoFile &$isoFile, int $blockSize, int $location, int $dataLength, string $destinationFile): void diff --git a/src/Util/Buffer.php b/src/Util/Buffer.php index 7ebc423..3bf57d2 100644 --- a/src/Util/Buffer.php +++ b/src/Util/Buffer.php @@ -33,7 +33,9 @@ public static function getString(array &$buffer, int $length, int &$offset = 0, if (! isset($buffer[$i])) { throw new Exception('Failed to read buffer entry ' . $i); } - $string .= chr($buffer[$i]); + if ($supplementary || $buffer[$i] !== 0) { + $string .= chr($buffer[$i]); + } } if ($supplementary) { diff --git a/tests/IsoFileTest.php b/tests/IsoFileTest.php index ff87eee..4b0dd48 100644 --- a/tests/IsoFileTest.php +++ b/tests/IsoFileTest.php @@ -311,6 +311,7 @@ public function testDescriptorsDosIso(): void $this->assertSame('DOS4.01', $supplementaryVolumeDescriptor->volumeId); $this->assertSame(848, $supplementaryVolumeDescriptor->volumeSpaceSize); $this->assertSame('', $supplementaryVolumeDescriptor->appId); + $this->assertSame(3, $supplementaryVolumeDescriptor->jolietLevel); $this->assertNull($supplementaryVolumeDescriptor->creationDate); $this->assertNull($supplementaryVolumeDescriptor->modificationDate); $this->assertNull($supplementaryVolumeDescriptor->expirationDate);