From a674090de8cca1bc7afc40b3fddb6f411e4e8177 Mon Sep 17 00:00:00 2001 From: Rogiel Sulzbach Date: Wed, 6 Jan 2016 21:20:40 -0200 Subject: [PATCH] Initial commit --- .gitignore | 6 + README.md | 24 ++ composer.json | 21 ++ examples/01-open-file.php | 40 ++++ src/Compression/BZIPCompression.php | 42 ++++ src/Compression/Compression.php | 37 +++ src/Compression/DeflateCompression.php | 42 ++++ src/Encryption/DefaultEncryption.php | 103 ++++++++ src/Encryption/Encryption.php | 41 ++++ src/Hashing/BaseHashing.php | 73 ++++++ src/Hashing/FileKeyHashing.php | 38 +++ src/Hashing/Hashing.php | 36 +++ src/Hashing/NameAHashing.php | 38 +++ src/Hashing/NameBHashing.php | 38 +++ src/Hashing/TableOffsetHashing.php | 38 +++ src/MPQFile.php | 231 ++++++++++++++++++ src/Metadata/Block.php | 171 ++++++++++++++ src/Metadata/BlockTable.php | 61 +++++ src/Metadata/Hash.php | 111 +++++++++ src/Metadata/HashTable.php | 64 +++++ src/Metadata/Header.php | 289 +++++++++++++++++++++++ src/Metadata/UserData.php | 84 +++++++ src/Stream/Block/BlockStream.php | 185 +++++++++++++++ src/Stream/Block/Sector.php | 129 ++++++++++ src/Stream/CompressedStream.php | 89 +++++++ src/Stream/EncryptedStream.php | 110 +++++++++ src/Stream/FileStream.php | 86 +++++++ src/Stream/MemoryStream.php | 80 +++++++ src/Stream/Parser/BinaryStreamParser.php | 72 ++++++ src/Stream/Stream.php | 67 ++++++ src/Util/CryptoUtils.php | 72 ++++++ 31 files changed, 2518 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 composer.json create mode 100644 examples/01-open-file.php create mode 100644 src/Compression/BZIPCompression.php create mode 100644 src/Compression/Compression.php create mode 100644 src/Compression/DeflateCompression.php create mode 100644 src/Encryption/DefaultEncryption.php create mode 100644 src/Encryption/Encryption.php create mode 100644 src/Hashing/BaseHashing.php create mode 100644 src/Hashing/FileKeyHashing.php create mode 100644 src/Hashing/Hashing.php create mode 100644 src/Hashing/NameAHashing.php create mode 100644 src/Hashing/NameBHashing.php create mode 100644 src/Hashing/TableOffsetHashing.php create mode 100644 src/MPQFile.php create mode 100644 src/Metadata/Block.php create mode 100644 src/Metadata/BlockTable.php create mode 100644 src/Metadata/Hash.php create mode 100644 src/Metadata/HashTable.php create mode 100644 src/Metadata/Header.php create mode 100644 src/Metadata/UserData.php create mode 100644 src/Stream/Block/BlockStream.php create mode 100644 src/Stream/Block/Sector.php create mode 100644 src/Stream/CompressedStream.php create mode 100644 src/Stream/EncryptedStream.php create mode 100644 src/Stream/FileStream.php create mode 100644 src/Stream/MemoryStream.php create mode 100644 src/Stream/Parser/BinaryStreamParser.php create mode 100644 src/Stream/Stream.php create mode 100644 src/Util/CryptoUtils.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d1ec8e5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# Created by .ignore support plugin (hsz.mobi) +### Composer template +composer.phar +vendor/ +composer.lock + diff --git a/README.md b/README.md new file mode 100644 index 0000000..ab6fa8b --- /dev/null +++ b/README.md @@ -0,0 +1,24 @@ +# A PHP library for MPQ reading + +This library allows you to read MPQ files from PHP. + +## Installation + +The recommended way of installing this library is using Composer. + + composer require "rogiel/mpq" + +## Example + + use Rogiel\MPQ\MPQFile; + + $file = MPQFile::parseFile(__DIR__.'/test.SC2Replay'); + $stream = $file->openStream('replay.details'); + while($data = $stream->readBytes(100)) { + echo $data; + } + +## TODO + +* Encrypted files (parcial support) +* File writing diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..b891c58 --- /dev/null +++ b/composer.json @@ -0,0 +1,21 @@ +{ + "name": "rogiel/mpq", + "type": "library", + "description": "A MPQ reader in PHP", + "keywords": ["MPQ", "File Management"], + "homepage": "https://github.com/rogiel/php-mpq", + "license": "MIT", + "authors": [{ + "name": "Rogiel Sulzbach", + "email": "rogiel@rogiel.com", + "homepage": "https://rogiel.com" + }], + "autoload": { + "psr-4": { + "Rogiel\\MPQ\\": "src/" + } + }, + "require": { + "php": ">=5.4" + } +} diff --git a/examples/01-open-file.php b/examples/01-open-file.php new file mode 100644 index 0000000..6eadfbe --- /dev/null +++ b/examples/01-open-file.php @@ -0,0 +1,40 @@ +openStream('replay.details'); +while($data = $stream->readBytes(100)) { + echo $data; +} \ No newline at end of file diff --git a/src/Compression/BZIPCompression.php b/src/Compression/BZIPCompression.php new file mode 100644 index 0000000..9cdfae7 --- /dev/null +++ b/src/Compression/BZIPCompression.php @@ -0,0 +1,42 @@ +key = $key; + $this->seed = ((0xEEEE << 16) | 0xEEEE); + CryptoUtils::initTable(); + } + + public function reset($key) { + $this->key = $key; + $this->seed = ((0xEEEE << 16) | 0xEEEE); + } + + public function decrypt($string, $length) { + $data = $this->createBlockArray($string, $length); + + $datalen = $length / 4; + for($i = 0;$i < $datalen;$i++) { + $this->seed = CryptoUtils::uPlus($this->seed,CryptoUtils::$cryptTable[0x400 + ($this->key & 0xFF)]); + $ch = $data[$i] ^ (CryptoUtils::uPlus($this->key,$this->seed)); + + $this->key = (CryptoUtils::uPlus(((~$this->key) << 0x15), 0x11111111)) | (CryptoUtils::rShift($this->key,0x0B)); + $this->seed = CryptoUtils::uPlus(CryptoUtils::uPlus(CryptoUtils::uPlus($ch,$this->seed),($this->seed << 5)),3); + $data[$i] = $ch & ((0xFFFF << 16) | 0xFFFF); + } + + return $this->createDataStream($data, $length / 4); + } + + public function encrypt($data, $length) { + $key = clone $this->key; + + $seed = ((0xEEEE << 16) | 0xEEEE); + $datalen = $length; + for($i = 0;$i < $datalen;$i++) { + $seed = CryptoUtils::uPlus($seed,CryptoUtils::$cryptTable[0x400 + ($key & 0xFF)]); + $ch = $data[$i] ^ (CryptoUtils::uPlus($key,$seed)); + + $key = (CryptoUtils::uPlus(((~$key) << 0x15), 0x11111111)) | (CryptoUtils::rShift($key,0x0B)); + $seed = CryptoUtils::uPlus(CryptoUtils::uPlus(CryptoUtils::uPlus($data[$i],$seed),($seed << 5)),3); + $data[$i] = $ch & ((0xFFFF << 16) | 0xFFFF); + } + return $data; + } + + public function getBlockSize() { + return 4; + } + + private function createBlockArray($string, $length) { + $data = array(); + for($i = 0; $i<$length / 4; $i++) { + $t = unpack("V", substr($string, 4*$i, 4)); + $data[$i] = $t[1]; + } + return $data; + } + + private function createDataStream($data, $length) { + $dataOutput = ''; + for($i = 0; $i<$length / 4; $i++) { + $dataOutput .= pack("V", $data[$i]); + } + return $dataOutput; + } + +} \ No newline at end of file diff --git a/src/Encryption/Encryption.php b/src/Encryption/Encryption.php new file mode 100644 index 0000000..566cef1 --- /dev/null +++ b/src/Encryption/Encryption.php @@ -0,0 +1,41 @@ +hashType = $hashType; + CryptoUtils::initTable(); + } + + public function hash($string) { + $seed1 = 0x7FED7FED; + $seed2 = ((0xEEEE << 16) | 0xEEEE); + $strLen = strlen($string); + + for ($i = 0;$i < $strLen;$i++) { + $next = ord(strtoupper(substr($string, $i, 1))); + + $seed1 = CryptoUtils::$cryptTable[($this->hashType << 8) + $next] ^ (CryptoUtils::uPlus($seed1,$seed2)); + $seed2 = CryptoUtils::uPlus(CryptoUtils::uPlus(CryptoUtils::uPlus(CryptoUtils::uPlus($next,$seed1),$seed2),$seed2 << 5),3); + } + return $seed1; + } + + +// function that adds up two integers without allowing them to overflow to floats + private function uPlus($o1, $o2) { + $o1h = ($o1 >> 16) & 0xFFFF; + $o1l = $o1 & 0xFFFF; + + $o2h = ($o2 >> 16) & 0xFFFF; + $o2l = $o2 & 0xFFFF; + + $ol = $o1l + $o2l; + $oh = $o1h + $o2h; + if ($ol > 0xFFFF) { $oh += (($ol >> 16) & 0xFFFF); } + return ((($oh << 16) & (0xFFFF << 16)) | ($ol & 0xFFFF)); + } + + +} \ No newline at end of file diff --git a/src/Hashing/FileKeyHashing.php b/src/Hashing/FileKeyHashing.php new file mode 100644 index 0000000..a0b8469 --- /dev/null +++ b/src/Hashing/FileKeyHashing.php @@ -0,0 +1,38 @@ +stream = $stream; + $this->parse(); + } + + // ----------------------------------------------------------------------------------------------------------------- + + private function parse() { + $parser = new BinaryStreamParser($this->stream); + + $signature = $this->parseSignature($parser); + if($signature == "MPQ27") { + $this->userData = UserData::parse($parser); + $this->stream->seek($this->userData->getHeaderOffset()); + } + + $signature = $this->parseSignature($parser); + if($signature == "MPQ26") { + $this->header = Header::parse($parser); + } + + $this->hashTable = $this->parseHashTable(); + $this->blockTable = $this->parseBlockTable(); + } + + private function parseHashTable() { + $hashing = new FileKeyHashing(); + $encryptedStream = new EncryptedStream($this->stream, new DefaultEncryption($hashing->hash('(hash table)'))); + $parser = new BinaryStreamParser($encryptedStream); + $parser->seek($this->userData->getHeaderOffset() + $this->header->getHashTablePos()); + $hashes = array(); + for($i = 0; $i<$this->header->getHashTableSize(); $i++) { + $hashes[$i] = Hash::parse($parser); + } + return new HashTable($hashes); + } + + private function parseBlockTable() { + $hashing = new FileKeyHashing(); + $encryptedStream = new EncryptedStream($this->stream, new DefaultEncryption($hashing->hash('(block table)'))); + $parser = new BinaryStreamParser($encryptedStream); + $parser->seek($this->userData->getHeaderOffset() + $this->header->getBlockTablePos()); + $blocks = array(); + for($i = 0; $i<$this->header->getBlockTableSize(); $i++) { + $blocks[$i] = Block::parse($parser); + } + return new BlockTable($blocks); + } + + private function parseSignature(BinaryStreamParser $parser) { + $signature = chr($parser->readByte()); + $signature .= chr($parser->readByte()); + $signature .= chr($parser->readByte()); + $signature .= $parser->readByte(); + + return $signature; + } + + // ----------------------------------------------------------------------------------------------------------------- + + /** + * @param $fileName + * @return null|Hash + */ + public function getFileHash($fileName) { + $hashingA = new NameAHashing(); + $hashingB = new NameBHashing(); + + $hashA = $hashingA->hash($fileName); + $hashB = $hashingB->hash($fileName); + + return $this->hashTable->findHashByHash($hashA, $hashB); + } + + /** + * @param $fileName + * @return null|Block + */ + public function getFileBlock($fileName) { + $hash = $this->getFileHash($fileName); + if($hash == NULL) { + return NULL; + } + return $this->getBlockTable()->getBlock($hash->getBlockIndex()); + } + + public function openStream($fileName) { + $block = $this->getFileBlock($fileName); + if($block == NULL) { + return NULL; + } + + $stream = clone $this->stream; + $stream->seek($this->userData->getHeaderOffset() + $block->getFilePos()); + $parser = new BinaryStreamParser($stream); + + $sectors = array(); + if($block->isChecksumed() || !$block->isSingleUnit()) { + $blockSize = $block->getCompressedSize(); + $fileSize = $block->getSize(); + + for ($i = $fileSize;$i > 0;$i -= $this->header->getBlockSize()) { + $sectors[] = $parser->readUInt32(); + $blockSize -= 4; + } + $sectors[] = $parser->readUInt32(); + } else { + $sectors = array( + 0, + $block->getCompressedSize() + ); + } + + return new BlockStream($this, $stream, $block, $sectors); + } + + // ----------------------------------------------------------------------------------------------------------------- + + /** + * @return UserData + */ + public function getUserData() { + return $this->userData; + } + + /** + * @return Header + */ + public function getHeader() { + return $this->header; + } + + /** + * @return HashTable + */ + public function getHashTable() { + return $this->hashTable; + } + + /** + * @return BlockTable + */ + public function getBlockTable() { + return $this->blockTable; + } + + // ----------------------------------------------------------------------------------------------------------------- + + public static function parseFile($file) { + return new MPQFile(new FileStream($file)); + } + + public static function parseString($string) { + return new MPQFile(new MemoryStream($string)); + } + +} diff --git a/src/Metadata/Block.php b/src/Metadata/Block.php new file mode 100644 index 0000000..a9dbd1e --- /dev/null +++ b/src/Metadata/Block.php @@ -0,0 +1,171 @@ +filePos = $parser->readUInt32(); + $block->compressedSize = $parser->readUInt32(); + $block->size = $parser->readUInt32(); + $block->flags = $parser->readUInt32(); + + return $block; + } + + // ----------------------------------------------------------------------------------------------------------------- + + /** + * @return mixed + */ + public function getFilePos() { + return $this->filePos; + } + + /** + * @return mixed + */ + public function getCompressedSize() { + return $this->compressedSize; + } + + /** + * @return mixed + */ + public function getSize() { + return $this->size; + } + + /** + * @return mixed + */ + public function getFlags() { + return $this->flags; + } + + // ----------------------------------------------------------------------------------------------------------------- + + public function isImploded() { + return ($this->flags & self::FLAG_IMPLODE) != 0; + } + + public function isCompressed() { + return ($this->flags & self::FLAG_COMPRESS) != 0; + } + + public function isEncrypted() { + return ($this->flags & self::FLAG_ENCRYPTED) != 0; + } + + public function isKeyBasedOnPosition() { + return ($this->flags & self::FLAG_FIX_KEY) != 0; + } + + public function isPatched() { + return ($this->flags & self::FLAG_PATCH_FILE) != 0; + } + + public function isSingleUnit() { + return ($this->flags & self::FLAG_SINGLE_UNIT) != 0; + } + + public function isDeleted() { + return ($this->flags & self::FLAG_DELETE_MARKER) != 0; + } + + public function isChecksumed() { + return ($this->flags & self::FLAG_SECTOR_CRC) != 0; + } + + public function isExisting() { + return ($this->flags & self::FLAG_EXISTS) != 0; + } + + +} \ No newline at end of file diff --git a/src/Metadata/BlockTable.php b/src/Metadata/BlockTable.php new file mode 100644 index 0000000..593a7ae --- /dev/null +++ b/src/Metadata/BlockTable.php @@ -0,0 +1,61 @@ +blocks = $blocks; + } + + // ----------------------------------------------------------------------------------------------------------------- + + /** + * @param $index + * @return Block + */ + public function getBlock($index) { + if(isset($this->blocks[$index])) { + return $this->blocks[$index]; + } + return NULL; + } + + /** + * @return mixed + */ + public function getBlocks() { + return $this->blocks; + } + +} \ No newline at end of file diff --git a/src/Metadata/Hash.php b/src/Metadata/Hash.php new file mode 100644 index 0000000..4351c4a --- /dev/null +++ b/src/Metadata/Hash.php @@ -0,0 +1,111 @@ +name1 = $parser->readUInt32(); + $hash->name2 = $parser->readUInt32(); + $hash->locale = $parser->readUInt16(); +// echo $hash->locale; +// die(); + + $hash->platform = $parser->readUInt16(); + $hash->blockIndex = $parser->readUInt32(); + + return $hash; + } + + // ----------------------------------------------------------------------------------------------------------------- + + /** + * @return mixed + */ + public function getName1() { + return $this->name1; + } + + /** + * @return mixed + */ + public function getName2() { + return $this->name2; + } + + /** + * @return mixed + */ + public function getLocale() { + return $this->locale; + } + + /** + * @return mixed + */ + public function getPlatform() { + return $this->platform; + } + + /** + * @return mixed + */ + public function getBlockIndex() { + return $this->blockIndex; + } + +} \ No newline at end of file diff --git a/src/Metadata/HashTable.php b/src/Metadata/HashTable.php new file mode 100644 index 0000000..137711f --- /dev/null +++ b/src/Metadata/HashTable.php @@ -0,0 +1,64 @@ +hashes = $hashes; + + // index + foreach($this->hashes as $hash) { + /** @var $hash Hash */ + $this->index[$hash->getName1()][$hash->getName2()] = $hash; + } + } + + // ----------------------------------------------------------------------------------------------------------------- + + public function findHashByHash($hashA, $hashB) { + if(isset($this->index[$hashA][$hashB])) { + return $this->index[$hashA][$hashB]; + } + return NULL; + } + + /** + * @return mixed + */ + public function getHashes() { + return $this->hashes; + } + +} \ No newline at end of file diff --git a/src/Metadata/Header.php b/src/Metadata/Header.php new file mode 100644 index 0000000..bdcf2c0 --- /dev/null +++ b/src/Metadata/Header.php @@ -0,0 +1,289 @@ +size = $parser->readUInt32(); + $header->archiveSize = $parser->readUInt32(); + $header->formatVersion = $parser->readUInt16(); + $header->blockSize = $parser->readUInt16(); + $header->hashTablePos = $parser->readUInt32(); + $header->blockTablePos = $parser->readUInt32(); + $header->hashTableSize = $parser->readUInt32(); + $header->blockTableSize = $parser->readUInt32(); + + if($header->formatVersion >= self::ARCHIVE_FORMAT_2) { + $parser->skip(8); //FIXME HiBlockTablePos64 + $header->hashTablePosHi = $parser->readUInt16(); + $header->blockTablePosHi = $parser->readUInt16(); + } + + // TODO implement other formats + + return $header; + } + + /** + * @return integer + */ + public function getSize() { + return $this->size; + } + + /** + * @return integer + */ + public function getArchiveSize() { + return $this->archiveSize; + } + + /** + * @return integer + */ + public function getFormatVersion() { + return $this->formatVersion; + } + + /** + * @return mixed + */ + public function getBlockSize() { + return $this->blockSize; + } + + /** + * @return mixed + */ + public function getHashTablePos() { + return $this->hashTablePos; + } + + /** + * @return mixed + */ + public function getBlockTablePos() { + return $this->blockTablePos; + } + + /** + * @return mixed + */ + public function getHashTableSize() { + return $this->hashTableSize; + } + + /** + * @return mixed + */ + public function getBlockTableSize() { + return $this->blockTableSize; + } + + /** + * @return mixed + */ + public function getHiBlockTablePos64() { + return $this->hiBlockTablePos64; + } + + /** + * @return mixed + */ + public function getHashTablePosHi() { + return $this->hashTablePosHi; + } + + /** + * @return mixed + */ + public function getBlockTablePosHi() { + return $this->blockTablePosHi; + } + + /** + * @return mixed + */ + public function getArchiveSize64() { + return $this->archiveSize64; + } + + /** + * @return mixed + */ + public function getBetTablePos64() { + return $this->betTablePos64; + } + + /** + * @return mixed + */ + public function getHetTablePos64() { + return $this->hetTablePos64; + } + + /** + * @return mixed + */ + public function getHashTableSize64() { + return $this->hashTableSize64; + } + + /** + * @return mixed + */ + public function getBlockTableSize64() { + return $this->blockTableSize64; + } + + /** + * @return mixed + */ + public function getHiBlockTableSize64() { + return $this->hiBlockTableSize64; + } + + /** + * @return mixed + */ + public function getHetTableSize64() { + return $this->hetTableSize64; + } + + /** + * @return mixed + */ + public function getBetTableSize64() { + return $this->betTableSize64; + } + + /** + * @return mixed + */ + public function getRawChunkSize() { + return $this->rawChunkSize; + } + +} \ No newline at end of file diff --git a/src/Metadata/UserData.php b/src/Metadata/UserData.php new file mode 100644 index 0000000..b837ca5 --- /dev/null +++ b/src/Metadata/UserData.php @@ -0,0 +1,84 @@ +size = $parser->readUInt32(); + $data->headerOffset = $parser->readUInt32(); + $data->header = $parser->readUInt32(); + $data->rawContent = $parser->readBytes($data->size - 12); + + return $data; + } + + // ----------------------------------------------------------------------------------------------------------------- + + /** + * @return integer + */ + public function getSize() { + return $this->size; + } + + /** + * @return integer + */ + public function getHeader() { + return $this->header; + } + + /** + * @return integer + */ + public function getHeaderOffset() { + return $this->headerOffset; + } + + /** + * @return array + */ + public function getRawContent() { + return $this->rawContent; + } + + +} \ No newline at end of file diff --git a/src/Stream/Block/BlockStream.php b/src/Stream/Block/BlockStream.php new file mode 100644 index 0000000..0545263 --- /dev/null +++ b/src/Stream/Block/BlockStream.php @@ -0,0 +1,185 @@ +file = $file; + $this->stream = $stream; + $this->block = $block; + + $this->sectors = array(); + $c = count($sectors) - 1; + for ($i = 0; $i < $c; $i++) { + $this->sectors[] = new Sector($i, $sectors[$i], $sectors[$i + 1]); + } + + $this->position = 0; + $this->buffer = NULL; + $this->currentSector = $this->sectors[0]; + } + + // ----------------------------------------------------------------------------------------------------------------- + + public function close() { + $this->stream->close(); + } + + public function readByte() { + return $this->readBytes(1); + } + + public function readBytes($bytes) { + if($this->position >= $this->block->getSize()) { + return false; + } + if(($this->position + $bytes) > $this->block->getSize()) { + $bytes = $this->block->getSize() - $this->position; + } + + if($this->buffer == NULL) { + $this->buffer = $this->readSector($this->currentSector); + } else if($this->positionInSector >= strlen($this->buffer)) { + $this->currentSector = $this->sectors[$this->currentSector->getIndex() + 1]; + $this->buffer = $this->readSector($this->currentSector); + $this->positionInSector = 0; + } + + $data = substr( + $this->buffer, + $this->positionInSector, + $bytes + ); + $this->position += strlen($data); + $this->positionInSector += strlen($data); + + return $data; + } + + public function seek($position) { + if($this->block->isCompressed()) { + throw new \RuntimeException("Seek is not supported on compressed streams"); + } + $this->position = $position; + } + + public function skip($bytes) { + if($this->block->isCompressed()) { + throw new \RuntimeException("Seek is not supported on compressed streams"); + } + $this->position += $bytes; + } + + // ----------------------------------------------------------------------------------------------------------------- + + private function readSector(Sector $sector) { + $this->stream->seek($this->file->getUserData()->getHeaderOffset() + + $this->block->getFilePos() + + $sector->getStart()); + + $compressedStream = $this->createCompressedStream(); + return $compressedStream->readBytes($sector->getLength()); + } + + private function createCompressedStream() { + $stream = $this->stream; + if($this->block->isCompressed()) { + $parser = new BinaryStreamParser($this->stream); + $compressionType = $parser->readByte(); + switch ($compressionType) { + case 0x10: + return new CompressedStream($stream, new BZIPCompression()); + break; + } + } + return $stream; + } + + private function computeSectors($start, $length) { + $sectors = array(); + foreach($this->sectors as $sector) { + /** @var $sector Sector */ + if($sector->contains($start, $length)) { + $sectors[] = $sector; + } + } + return $sectors; + } + +} \ No newline at end of file diff --git a/src/Stream/Block/Sector.php b/src/Stream/Block/Sector.php new file mode 100644 index 0000000..71ec89e --- /dev/null +++ b/src/Stream/Block/Sector.php @@ -0,0 +1,129 @@ +index = $index; + $this->start = $start; + $this->end = $end; + } + + // ----------------------------------------------------------------------------------------------------------------- + + /** + * @return mixed + */ + public function getIndex() { + return $this->index; + } + + /** + * @return mixed + */ + public function getStart() { + return $this->start; + } + + /** + * @return mixed + */ + public function getEnd() { + return $this->end; + } + + public function getLength() { + return $this->end - $this->start; + } + + // ----------------------------------------------------------------------------------------------------------------- + + public function intersectionBegin($start, $length) { + if($start < $this->start) { + return $this->start; + } + return $start - $this->start; + } + + public function intersectionEnd($start, $length) { + if(($start + $length) > $this->end) { + return $this->getLength(); + } + return $length; + } + + public function contains($start, $length) { + if($start >= $this->start) { + if($start <= ($this->end)) { + return true; + } + } + return false; + } + + public function fullyContains($start, $length) { + if($start >= $this->start) { + if(($start + $length) <= ($this->end)) { + return true; + } + } + return false; + } + +} \ No newline at end of file diff --git a/src/Stream/CompressedStream.php b/src/Stream/CompressedStream.php new file mode 100644 index 0000000..8f1d6af --- /dev/null +++ b/src/Stream/CompressedStream.php @@ -0,0 +1,89 @@ +stream = $stream; + $this->compression = $compression; + } + + // ----------------------------------------------------------------------------------------------------------------- + + /** + * {@inheritdoc} + */ + public function close() { + $this->stream->close(); + } + + /** + * {@inheritdoc} + */ + public function readByte() { + return $this->readBytes(1); + } + + /** + * {@inheritdoc} + */ + public function readBytes($bytes) { + return $this->compression->decompress($this->stream->readBytes($bytes), $bytes); + } + + /** + * {@inheritdoc} + */ + public function seek($position) { + $this->stream->seek($position); + } + + /** + * {@inheritdoc} + */ + public function skip($position) { + $this->stream->skip($position); + } + +} \ No newline at end of file diff --git a/src/Stream/EncryptedStream.php b/src/Stream/EncryptedStream.php new file mode 100644 index 0000000..dd6e006 --- /dev/null +++ b/src/Stream/EncryptedStream.php @@ -0,0 +1,110 @@ +stream = $stream; + $this->encryption = $encryption; + + $this->bufferPointer = 0xFF; + } + + // ----------------------------------------------------------------------------------------------------------------- + + /** + * {@inheritdoc} + */ + public function close() { + $this->stream->close(); + } + + /** + * {@inheritdoc} + */ + public function readByte() { + return $this->readBytes(1); + } + + /** + * {@inheritdoc} + */ + public function readBytes($bytes) { + $data = ''; + $remaining = $bytes; + + for($block = 0; $block < ceil($bytes / $this->encryption->getBlockSize()); $block++) { + if($this->bufferPointer >= $this->encryption->getBlockSize()) { + $this->bufferPointer = 0; + $buffer = $this->stream->readBytes($this->encryption->getBlockSize()); + $this->buffer = $this->encryption->decrypt($buffer, $this->encryption->getBlockSize()); + } + $data .= substr($this->buffer, $this->bufferPointer, $remaining); + $this->bufferPointer += ($remaining > $this->encryption->getBlockSize() ? $this->encryption->getBlockSize() : $remaining); + $remaining -= $this->encryption->getBlockSize(); + } + + return $data; + } + + /** + * {@inheritdoc} + */ + public function seek($position) { + $this->stream->seek($position); + $this->bufferPointer = 0xFF; + } + + /** + * {@inheritdoc} + */ + public function skip($position) { + $this->stream->skip($position); + $this->bufferPointer = 0xFF; + } + +} \ No newline at end of file diff --git a/src/Stream/FileStream.php b/src/Stream/FileStream.php new file mode 100644 index 0000000..2f5c94d --- /dev/null +++ b/src/Stream/FileStream.php @@ -0,0 +1,86 @@ +file = $file; + $this->handle = fopen($file, 'r'); + } + + // ----------------------------------------------------------------------------------------------------------------- + + /** + * {@inheritdoc} + */ + public function close() { + fclose($this->handle); + } + + /** + * {@inheritdoc} + */ + public function readByte() { + return fread($this->handle, 1); + } + + /** + * {@inheritdoc} + */ + public function readBytes($bytes) { + return fread($this->handle, $bytes); + } + + /** + * {@inheritdoc} + */ + public function seek($position) { + fseek($this->handle, $position); + } + + /** + * {@inheritdoc} + */ + public function skip($position) { + fseek($this->handle, $position, SEEK_CUR); + } + + // ----------------------------------------------------------------------------------------------------------------- + + public function __clone() { + return new FileStream($this->file); + } + +} \ No newline at end of file diff --git a/src/Stream/MemoryStream.php b/src/Stream/MemoryStream.php new file mode 100644 index 0000000..6c31f77 --- /dev/null +++ b/src/Stream/MemoryStream.php @@ -0,0 +1,80 @@ +data = $data; + $this->pointer = 0; + } + + // ----------------------------------------------------------------------------------------------------------------- + + /** + * {@inheritdoc} + */ + public function close() {} + + /** + * {@inheritdoc} + */ + public function readByte() { + return $this->readBytes(1); + } + + /** + * {@inheritdoc} + */ + public function readBytes($bytes) { + $data = substr($this->data, $this->pointer, $bytes); + $this->pointer += strlen($data); + return $data; + } + + /** + * {@inheritdoc} + */ + public function seek($position) { + $this->pointer = $position; + } + + /** + * {@inheritdoc} + */ + public function skip($position) { + $this->pointer += $position; + } + +} \ No newline at end of file diff --git a/src/Stream/Parser/BinaryStreamParser.php b/src/Stream/Parser/BinaryStreamParser.php new file mode 100644 index 0000000..3964a7d --- /dev/null +++ b/src/Stream/Parser/BinaryStreamParser.php @@ -0,0 +1,72 @@ +stream = $stream; + } + + public function seek($position) { + $this->stream->seek($position); + } + + public function skip($bytes) { + $this->stream->skip($bytes); + } + + // read little endian 32-bit integer + public function readUInt32() { + $t = unpack("V", $this->stream->readBytes(4)); + return $t[1]; + } + + public function readUInt16() { + $t = unpack("v", $this->stream->readBytes(2)); + return $t[1]; + } + public function readByte() { + $t = unpack("C", $this->stream->readBytes(1)); + return $t[1]; + } + + public function readBytes($size) { + return $this->stream->readBytes($size); + } + +} \ No newline at end of file diff --git a/src/Stream/Stream.php b/src/Stream/Stream.php new file mode 100644 index 0000000..5aaa579 --- /dev/null +++ b/src/Stream/Stream.php @@ -0,0 +1,67 @@ +> 16) & 0xFFFF; + $o1l = $o1 & 0xFFFF; + + $o2h = ($o2 >> 16) & 0xFFFF; + $o2l = $o2 & 0xFFFF; + + $ol = $o1l + $o2l; + $oh = $o1h + $o2h; + if ($ol > 0xFFFF) { $oh += (($ol >> 16) & 0xFFFF); } + return ((($oh << 16) & (0xFFFF << 16)) | ($ol & 0xFFFF)); + } + + public static function rShift($num,$bits) { + return (($num >> 1) & 0x7FFFFFFF) >> ($bits - 1); + } + +} \ No newline at end of file