public function parseHeader() { $fp = 0; $headerParsed = false; $headerOffset = 0; while (!$headerParsed) { $magic = unpack("c4", self::readBytes($this->fileData, $fp, 4)); // MPQ 1Bh or 1Ah if ($magic[1] != 0x4d || $magic[2] != 0x50 || $magic[3] != 0x51) { $this->init = false; return false; } if ($magic[4] == 27) { // user data block (1Bh) if ($this->debug) { $this->debug(sprintf("Found user data block at %08X", $fp)); } $uDataMaxSize = self::readUInt32($this->fileData, $fp); $headerOffset = self::readUInt32($this->fileData, $fp); $this->headerOffset = $headerOffset; $uDataSize = self::readUInt32($this->fileData, $fp); $uDataStart = $fp; $userDataArray = MPQFile::parseSerializedData($this->fileData, $fp); $this->verMajor = $userDataArray[1][1]; $this->build = $userDataArray[1][4]; $this->gameLen = ceil($userDataArray[3] / 16); $this->versionString = sprintf("%d.%d.%d.%d", $this->verMajor, $userDataArray[1][2], $userDataArray[1][3], $this->build); $fp = $headerOffset; } elseif ($magic[4] == 26) { // header (1Ah) if ($this->debug) { $this->debug(sprintf("Found header at %08X", $fp)); } $headerSize = self::readUInt32($this->fileData, $fp); $archiveSize = self::readUInt32($this->fileData, $fp); $this->archiveSize = $archiveSize; $formatVersion = self::readUInt16($this->fileData, $fp); $sectorSizeShift = self::readByte($this->fileData, $fp); $sectorSize = 512 * pow(2, $sectorSizeShift); $this->sectorSize = $sectorSize; $fp++; $hashTableOffset = self::readUInt32($this->fileData, $fp) + $headerOffset; $this->hashTableOffset = $hashTableOffset; $blockTableOffset = self::readUInt32($this->fileData, $fp) + $headerOffset; $this->blockTableOffset = $blockTableOffset; if ($this->debug) { $this->debug(sprintf("Hash table offset: %08X, Block table offset: %08X", $hashTableOffset, $blockTableOffset)); } $hashTableEntries = self::readUInt32($this->fileData, $fp); $this->hashTableSize = $hashTableEntries; $blockTableEntries = self::readUInt32($this->fileData, $fp); $this->blockTableSize = $blockTableEntries; $headerParsed = true; } else { if ($this->debug) { $this->debug("Could not find MPQ header"); } return false; } } // read and decode the hash table $fp = $hashTableOffset; $hashSize = $hashTableEntries * 4; // hash table size in 4-byte chunks $tmp = array(); for ($i = 0; $i < $hashSize; $i++) { $tmp[$i] = self::readUInt32($this->fileData, $fp); } if ($this->debug) { $this->debug("Encrypted hash table:"); $this->printTable($tmp); } $hashTable = self::decryptStuff($tmp, self::hashStuff("(hash table)", self::$MPQ_HASH_FILE_KEY)); if ($this->debug) { $this->debug("DEBUG: Hash table"); $this->debug("HashA, HashB, Language+platform, Fileblockindex"); $tmpnewline = $this->debugNewline; $this->debugNewline = ""; for ($i = 0; $i < $hashTableEntries; $i++) { $filehashA = $hashTable[$i * 4]; $filehashB = $hashTable[$i * 4 + 1]; $lanplat = $hashTable[$i * 4 + 2]; $blockindex = $hashTable[$i * 4 + 3]; $this->debug(sprintf("<pre>%08X %08X %08X %08X</pre>", $filehashA, $filehashB, $lanplat, $blockindex)); } $this->debugNewline = $tmpnewline; } // read and decode the block table $fp = $blockTableOffset; $blockSize = $blockTableEntries * 4; // block table size in 4-byte chunks $tmp = array(); for ($i = 0; $i < $blockSize; $i++) { $tmp[$i] = self::readUInt32($this->fileData, $fp); } if ($this->debug) { $this->debug("Encrypted block table:"); $this->printTable($tmp); } $blockTable = self::decryptStuff($tmp, self::hashStuff("(block table)", self::$MPQ_HASH_FILE_KEY)); $this->hashtable = $hashTable; $this->blocktable = $blockTable; if ($this->debug) { $this->debug("DEBUG: Block table"); $this->debug("Offset, Blocksize, Filesize, flags"); $tmpnewline = $this->debugNewline; $this->debugNewline = ""; for ($i = 0; $i < $blockTableEntries; $i++) { $blockIndex = $i * 4; $blockOffset = $this->blocktable[$blockIndex] + $this->headerOffset; $blockSize = $this->blocktable[$blockIndex + 1]; $fileSize = $this->blocktable[$blockIndex + 2]; $flags = $this->blocktable[$blockIndex + 3]; $this->debug(sprintf("<pre>%08X %8d %8d %08X</pre>", $blockOffset, $blockSize, $fileSize, $flags)); } $this->debugNewline = $tmpnewline; } $this->init = true; if ($this->getFileSize("replay.details") > 0 && $this->getFileSize("replay.initData") > 0) { $this->fileType = "SC2replay"; } elseif ($this->getFileSize("DocumentHeader") > 0 && $this->getFileSize("Minimap.tga") > 0) { $this->fileType = "SC2map"; } else { $this->fileType = "Unknown"; } return true; }
function parseDetailsFile($string) { $numByte = 0; $array = MPQFile::parseSerializedData($string, $numByte); $playerArray = $array[0]; foreach ($playerArray as $index => $player) { if ($this->debug) { echo "<pre>"; print_r($player); echo "</pre>"; } $p = array(); $p["name"] = $player[0]; $p["uid"] = $player[1][4]; $p["uidIndex"] = $player[1][2]; $p["color"] = sprintf("%02X%02X%02X", $player[3][1], $player[3][2], $player[3][3]); $p["apmtotal"] = 0; $p["apm"] = array(); $p["firstevents"] = array(); $p["numevents"] = array(); $p["ptype"] = ""; $p["handicap"] = 0; $p["team"] = 0; $p["lrace"] = $player[2]; // locale-specific player race $p["race"] = ""; // player race in english, populated by checking which workers they build $p["id"] = $index + 1; if ($p["uid"] == 0) { $p["isComp"] = true; } else { $p["isComp"] = false; } $p["isObs"] = false; $p["difficulty"] = ""; $p["sColor"] = ""; if ($player[8] == 1) { $p["won"] = $player[8]; $this->winnerKnown = true; } $this->players[$index + 1] = $p; } $this->mapName = $array[1]; $this->gameFiletime = $array[5]; $this->gameCtime = floor(($array[5] - 116444735995904000) / 10000000); }