1
0
mirror of https://github.com/Rogiel/php-mpq synced 2025-12-06 00:13:04 +00:00

Initial commit

This commit is contained in:
2016-01-06 21:20:40 -02:00
commit a674090de8
31 changed files with 2518 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
# Created by .ignore support plugin (hsz.mobi)
### Composer template
composer.phar
vendor/
composer.lock

24
README.md Normal file
View File

@@ -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

21
composer.json Normal file
View File

@@ -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"
}
}

40
examples/01-open-file.php Normal file
View File

@@ -0,0 +1,40 @@
<?php
/**
* Copyright (c) 2016, Rogiel Sulzbach
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* You must run "composer install" in order to generate the class loader
*/
require_once __DIR__.'/../vendor/autoload.php';
use Rogiel\MPQ\MPQFile;
$file = MPQFile::parseFile(__DIR__.'/../../test.SC2Replay');
$stream = $file->openStream('replay.details');
while($data = $stream->readBytes(100)) {
echo $data;
}

View File

@@ -0,0 +1,42 @@
<?php
/**
* Copyright (c) 2016, Rogiel Sulzbach
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Rogiel\MPQ\Compression;
class BZIPCompression implements Compression {
public function compress($data, $length) {
return bzcompress(substr($data, 0, $length));
}
public function decompress($data, $length) {
return bzdecompress(substr($data, 0, $length));
}
}

View File

@@ -0,0 +1,37 @@
<?php
/**
* Copyright (c) 2016, Rogiel Sulzbach
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Rogiel\MPQ\Compression;
interface Compression {
public function compress($data, $length);
public function decompress($data, $length);
}

View File

@@ -0,0 +1,42 @@
<?php
/**
* Copyright (c) 2016, Rogiel Sulzbach
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Rogiel\MPQ\Compression;
class DeflateCompression implements Compression {
public function compress($data, $length) {
return gzdeflate(substr($data, 0, $length));
}
public function decompress($data, $length) {
return gzinflate($data, $length);
}
}

View File

@@ -0,0 +1,103 @@
<?php
/**
* Copyright (c) 2016, Rogiel Sulzbach
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Rogiel\MPQ\Encryption;
use Rogiel\MPQ\Util\CryptoUtils;
class DefaultEncryption implements Encryption {
private $key;
private $seed;
public function __construct($key) {
$this->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;
}
}

View File

@@ -0,0 +1,41 @@
<?php
/**
* Copyright (c) 2016, Rogiel Sulzbach
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Rogiel\MPQ\Encryption;
interface Encryption {
public function reset($key);
public function encrypt($data, $length);
public function decrypt($data, $length);
public function getBlockSize();
}

View File

@@ -0,0 +1,73 @@
<?php
/**
* Copyright (c) 2016, Rogiel Sulzbach
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Rogiel\MPQ\Hashing;
use Rogiel\MPQ\Util\CryptoUtils;
abstract class BaseHashing implements Hashing {
private $hashType;
public function __construct($hashType) {
$this->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));
}
}

View File

@@ -0,0 +1,38 @@
<?php
/**
* Copyright (c) 2016, Rogiel Sulzbach
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Rogiel\MPQ\Hashing;
class FileKeyHashing extends BaseHashing {
public function __construct() {
parent::__construct(3);
}
}

36
src/Hashing/Hashing.php Normal file
View File

@@ -0,0 +1,36 @@
<?php
/**
* Copyright (c) 2016, Rogiel Sulzbach
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Rogiel\MPQ\Hashing;
interface Hashing {
public function hash($string);
}

View File

@@ -0,0 +1,38 @@
<?php
/**
* Copyright (c) 2016, Rogiel Sulzbach
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Rogiel\MPQ\Hashing;
class NameAHashing extends BaseHashing {
public function __construct() {
parent::__construct(1);
}
}

View File

@@ -0,0 +1,38 @@
<?php
/**
* Copyright (c) 2016, Rogiel Sulzbach
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Rogiel\MPQ\Hashing;
class NameBHashing extends BaseHashing {
public function __construct() {
parent::__construct(2);
}
}

View File

@@ -0,0 +1,38 @@
<?php
/**
* Copyright (c) 2016, Rogiel Sulzbach
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Rogiel\MPQ\Hashing;
class TableOffsetHashing extends BaseHashing {
public function __construct() {
parent::__construct(0);
}
}

231
src/MPQFile.php Normal file
View File

@@ -0,0 +1,231 @@
<?php
/**
* Copyright (c) 2016, Rogiel Sulzbach
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Rogiel\MPQ;
use Rogiel\MPQ\Encryption\DefaultEncryption;
use Rogiel\MPQ\Hashing\FileKeyHashing;
use Rogiel\MPQ\Hashing\NameAHashing;
use Rogiel\MPQ\Hashing\NameBHashing;
use Rogiel\MPQ\Metadata\Block;
use Rogiel\MPQ\Metadata\BlockTable;
use Rogiel\MPQ\Metadata\Hash;
use Rogiel\MPQ\Metadata\HashTable;
use Rogiel\MPQ\Metadata\Header;
use Rogiel\MPQ\Metadata\UserData;
use Rogiel\MPQ\Stream\Block\BlockStream;
use Rogiel\MPQ\Stream\EncryptedStream;
use Rogiel\MPQ\Stream\FileStream;
use Rogiel\MPQ\Stream\MemoryStream;
use Rogiel\MPQ\Stream\Parser\BinaryStreamParser;
use Rogiel\MPQ\Stream\Stream;
class MPQFile {
/**
* @var Stream
*/
private $stream;
/**
* @var UserData
*/
private $userData;
/**
* @var Header
*/
private $header;
/**
* @var HashTable
*/
private $hashTable;
/**
* @var BlockTable
*/
private $blockTable;
public function __construct(Stream $stream) {
$this->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));
}
}

171
src/Metadata/Block.php Normal file
View File

@@ -0,0 +1,171 @@
<?php
/**
* Copyright (c) 2016, Rogiel Sulzbach
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Rogiel\MPQ\Metadata;
use Rogiel\MPQ\Stream\Parser\BinaryStreamParser;
class Block {
/**
* File is compressed using PKWARE Data compression library
*/
const FLAG_IMPLODE = 0x00000100;
/**
* File is compressed using combination of compression methods
*/
const FLAG_COMPRESS = 0x00000200;
/**
* The file is encrypted
*/
const FLAG_ENCRYPTED = 0x00010000;
/**
* The decryption key for the file is altered according to the position of the file in the archive
*/
const FLAG_FIX_KEY = 0x00020000;
/**
* The file contains incremental patch for an existing file in base MPQ
*/
const FLAG_PATCH_FILE = 0x00100000;
/**
* Instead of being divided to 0x1000-bytes blocks, the file is stored as single unit
*/
const FLAG_SINGLE_UNIT = 0x01000000;
/**
* File is a deletion marker, indicating that the file no longer exists. This is used to allow patch archives to delete files present in lower-priority archives in the search chain. The file usually has length of 0 or 1 byte and its name is a hash
*/
const FLAG_DELETE_MARKER = 0x02000000;
/**
* File has checksums for each sector (explained in the File Data section). Ignored if file is not compressed or imploded.
*/
const FLAG_SECTOR_CRC = 0x04000000;
/**
* Set if file exists, reset when the file was deleted
*/
const FLAG_EXISTS = 0x80000000;
// -----------------------------------------------------------------------------------------------------------------
// Offset of the beginning of the file data, relative to the beginning of the archive.
private $filePos;
// Compressed file size
private $compressedSize;
// Size of uncompressed file
private $size;
// Flags for the file. See the table below for more informations
private $flags;
// -----------------------------------------------------------------------------------------------------------------
public static function parse(BinaryStreamParser $parser) {
$block = new Block();
$block->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;
}
}

View File

@@ -0,0 +1,61 @@
<?php
/**
* Copyright (c) 2016, Rogiel Sulzbach
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Rogiel\MPQ\Metadata;
class BlockTable {
private $blocks;
// -----------------------------------------------------------------------------------------------------------------
public function __construct($blocks) {
$this->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;
}
}

111
src/Metadata/Hash.php Normal file
View File

@@ -0,0 +1,111 @@
<?php
/**
* Copyright (c) 2016, Rogiel Sulzbach
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Rogiel\MPQ\Metadata;
use Rogiel\MPQ\Stream\Parser\BinaryStreamParser;
class Hash {
// The hash of the full file name (part A)
private $name1;
// The hash of the full file name (part B)
private $name2;
// The language of the file. This is a Windows LANGID data type, and uses the same values.
// 0 indicates the default language (American English), or that the file is language-neutral.
private $locale;
// The platform the file is used for. 0 indicates the default platform.
// No other values have been observed.
private $platform;
// If the hash table entry is valid, this is the index into the block table of the file.
// Otherwise, one of the following two values:
// - FFFFFFFFh: Hash table entry is empty, and has always been empty.
// Terminates searches for a given file.
// - FFFFFFFEh: Hash table entry is empty, but was valid at some point (a deleted file).
// Does not terminate searches for a given file.
private $blockIndex;
// -----------------------------------------------------------------------------------------------------------------
public static function parse(BinaryStreamParser $parser) {
$hash = new Hash();
$hash->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;
}
}

View File

@@ -0,0 +1,64 @@
<?php
/**
* Copyright (c) 2016, Rogiel Sulzbach
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Rogiel\MPQ\Metadata;
class HashTable {
private $hashes;
private $index;
// -----------------------------------------------------------------------------------------------------------------
public function __construct($hashes) {
$this->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;
}
}

289
src/Metadata/Header.php Normal file
View File

@@ -0,0 +1,289 @@
<?php
/**
* Copyright (c) 2016, Rogiel Sulzbach
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Rogiel\MPQ\Metadata;
use Rogiel\MPQ\Stream\Parser\BinaryStreamParser;
class Header {
const ARCHIVE_FORMAT_1 = 0;
const ARCHIVE_FORMAT_2 = 1;
const ARCHIVE_FORMAT_3 = 2;
const ARCHIVE_FORMAT_4 = 3;
// Size of the archive header
private $size;
// Size of MPQ archive
// This field is deprecated in the Burning Crusade MoPaQ format, and the size of the archive
// is calculated as the size from the beginning of the archive to the end of the hash table,
// block table, or extended block table (whichever is largest).
private $archiveSize;
// 0 = Format 1 (up to The Burning Crusade)
// 1 = Format 2 (The Burning Crusade and newer)
// 2 = Format 3 (WoW - Cataclysm beta or newer)
// 3 = Format 4 (WoW - Cataclysm beta or newer)
private $formatVersion;
// Power of two exponent specifying the number of 512-byte disk sectors in each logical sector
// in the archive. The size of each logical sector in the archive is 512 * 2^wBlockSize.
private $blockSize;
// Offset to the beginning of the hash table, relative to the beginning of the archive.
private $hashTablePos;
// Offset to the beginning of the block table, relative to the beginning of the archive.
private $blockTablePos;
// Number of entries in the hash table. Must be a power of two, and must be less than 2^16 for
// the original MoPaQ format, or less than 2^20 for the Burning Crusade format.
private $hashTableSize;
// Number of entries in the block table
private $blockTableSize;
//-- MPQ HEADER v 2 -------------------------------------------
// Offset to the beginning of array of 16-bit high parts of file offsets.
private $hiBlockTablePos64;
// High 16 bits of the hash table offset for large archives.
private $hashTablePosHi;
// High 16 bits of the block table offset for large archives.
private $blockTablePosHi;
//-- MPQ HEADER v 3 -------------------------------------------
// 64-bit version of the archive size
private $archiveSize64;
// 64-bit position of the BET table
private $betTablePos64;
// 64-bit position of the HET table
private $hetTablePos64;
//-- MPQ HEADER v 4 -------------------------------------------
// Compressed size of the hash table
private $hashTableSize64;
// Compressed size of the block table
private $blockTableSize64;
// Compressed size of the hi-block table
private $hiBlockTableSize64;
// Compressed size of the HET block
private $hetTableSize64;
// Compressed size of the BET block
private $betTableSize64;
// Size of raw data chunk to calculate MD5.
// MD5 of each data chunk follows the raw file data.
private $rawChunkSize;
// Array of MD5's
//unsigned char MD5_BlockTable[MD5_DIGEST_SIZE]; // MD5 of the block table before decryption
//unsigned char MD5_HashTable[MD5_DIGEST_SIZE]; // MD5 of the hash table before decryption
//unsigned char MD5_HiBlockTable[MD5_DIGEST_SIZE]; // MD5 of the hi-block table
//unsigned char MD5_BetTable[MD5_DIGEST_SIZE]; // MD5 of the BET table before decryption
//unsigned char MD5_HetTable[MD5_DIGEST_SIZE]; // MD5 of the HET table before decryption
//unsigned char MD5_MpqHeader[MD5_DIGEST_SIZE]; // MD5 of the MPQ header from signature to (including) MD5_HetTable
// -----------------------------------------------------------------------------------------------------------------
public static function parse(BinaryStreamParser $parser) {
$header = new Header();
$header->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;
}
}

84
src/Metadata/UserData.php Normal file
View File

@@ -0,0 +1,84 @@
<?php
/**
* Copyright (c) 2016, Rogiel Sulzbach
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Rogiel\MPQ\Metadata;
use Rogiel\MPQ\Stream\Parser\BinaryStreamParser;
class UserData {
private $size;
private $headerOffset;
private $header;
private $rawContent;
// -----------------------------------------------------------------------------------------------------------------
public static function parse(BinaryStreamParser $parser) {
$data = new UserData();
$data->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;
}
}

View File

@@ -0,0 +1,185 @@
<?php
/**
* Copyright (c) 2016, Rogiel Sulzbach
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Rogiel\MPQ\Stream\Block;
use Rogiel\MPQ\Compression\BZIPCompression;
use Rogiel\MPQ\Metadata\Block;
use Rogiel\MPQ\MPQFile;
use Rogiel\MPQ\Stream\CompressedStream;
use Rogiel\MPQ\Stream\Parser\BinaryStreamParser;
use Rogiel\MPQ\Stream\Stream;
class BlockStream implements Stream {
/**
* @var MPQFile
*/
private $file;
/**
* @var Stream
*/
private $stream;
/**
* @var Block
*/
private $block;
/**
* @var array
*/
private $sectors;
// -----------------------------------------------------------------------------------------------------------------
private $position;
private $buffer;
private $positionInSector;
/**
* @var Sector
*/
private $currentSector;
// -----------------------------------------------------------------------------------------------------------------
/**
* BlockStream constructor.
* @param MPQFile $file
* @param Stream $stream
* @param Block $block
* @param array $sectors
*/
public function __construct(MPQFile $file, Stream $stream, Block $block, array $sectors) {
$this->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;
}
}

129
src/Stream/Block/Sector.php Normal file
View File

@@ -0,0 +1,129 @@
<?php
/**
* Copyright (c) 2016, Rogiel Sulzbach
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Rogiel\MPQ\Stream\Block;
class Sector {
/**
* The sector index
*
* @var integer
*/
private $index;
/**
* The sector starting position (relative to the parent block)
*
* @var integer
*/
private $start;
/**
* The sector ending position (relative to the parent block)
*
* @var integer
*/
private $end;
// -----------------------------------------------------------------------------------------------------------------
/**
* Sector constructor.
* @param $index integer the sector index
* @param $start integer the sector starting position (relative to the parent block)
* @param $end integer the sector ending position (relative to the parent block)
*/
public function __construct($index, $start, $end) {
$this->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;
}
}

View File

@@ -0,0 +1,89 @@
<?php
/**
* Copyright (c) 2016, Rogiel Sulzbach
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Rogiel\MPQ\Stream;
use Rogiel\MPQ\Compression\Compression;
class CompressedStream implements Stream{
/**
* @var Stream
*/
private $stream;
/**
* @var Compression
*/
private $compression;
// -----------------------------------------------------------------------------------------------------------------
public function __construct(Stream $stream, Compression $compression) {
$this->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);
}
}

View File

@@ -0,0 +1,110 @@
<?php
/**
* Copyright (c) 2016, Rogiel Sulzbach
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Rogiel\MPQ\Stream;
use Rogiel\MPQ\Encryption\Encryption;
class EncryptedStream implements Stream{
/**
* @var Stream
*/
private $stream;
/**
* @var Encryption
*/
private $encryption;
private $buffer;
private $bufferPointer;
// -----------------------------------------------------------------------------------------------------------------
public function __construct(Stream $stream, Encryption $encryption) {
$this->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;
}
}

86
src/Stream/FileStream.php Normal file
View File

@@ -0,0 +1,86 @@
<?php
/**
* Copyright (c) 2016, Rogiel Sulzbach
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Rogiel\MPQ\Stream;
class FileStream implements Stream {
private $file;
private $handle;
// -----------------------------------------------------------------------------------------------------------------
public function __construct($file) {
$this->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);
}
}

View File

@@ -0,0 +1,80 @@
<?php
/**
* Copyright (c) 2016, Rogiel Sulzbach
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Rogiel\MPQ\Stream;
class MemoryStream implements Stream {
private $data;
private $pointer;
// -----------------------------------------------------------------------------------------------------------------
public function __construct($data) {
$this->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;
}
}

View File

@@ -0,0 +1,72 @@
<?php
/**
* Copyright (c) 2016, Rogiel Sulzbach
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Rogiel\MPQ\Stream\Parser;
use Rogiel\MPQ\Stream\Stream;
class BinaryStreamParser {
private $stream;
/**
* BinaryStreamParser constructor.
* @param Stream $stream
*/
public function __construct(Stream $stream) {
$this->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);
}
}

67
src/Stream/Stream.php Normal file
View File

@@ -0,0 +1,67 @@
<?php
/**
* Copyright (c) 2016, Rogiel Sulzbach
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Rogiel\MPQ\Stream;
interface Stream {
/**
* Closes the stream
*/
public function close();
/**
* Reads a single byte from the stream
*
* @return string
*/
public function readByte();
/**
* Read up to "$bytes" bytes from the stream
*
* @param $bytes integer the maximum number of bytes to read
* @return string a string with up to $bytes characters
*/
public function readBytes($bytes);
/**
* Seeks the stream into the given position
*
* @param $position integer the position to seek to
*/
public function seek($position);
/**
* Skips $bytes bytes in the stream
*
* @param $bytes integer the number of bytes to skip
*/
public function skip($bytes);
}

72
src/Util/CryptoUtils.php Normal file
View File

@@ -0,0 +1,72 @@
<?php
/**
* Copyright (c) 2016, Rogiel Sulzbach
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
namespace Rogiel\MPQ\Util;
class CryptoUtils {
public static $cryptTable;
public static function initTable() {
if (!self::$cryptTable) {
self::$cryptTable = array();
$seed = 0x00100001;
for ($index1 = 0; $index1 < 0x100; $index1++) {
for ($index2 = $index1, $i = 0; $i < 5; $i++, $index2 += 0x100) {
$seed = (self::uPlus($seed * 125, 3)) % 0x2AAAAB;
$temp1 = ($seed & 0xFFFF) << 0x10;
$seed = (self::uPlus($seed * 125, 3)) % 0x2AAAAB;
$temp2 = ($seed & 0xFFFF);
self::$cryptTable[$index2] = ($temp1 | $temp2);
}
}
}
}
public static 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));
}
public static function rShift($num,$bits) {
return (($num >> 1) & 0x7FFFFFFF) >> ($bits - 1);
}
}