Jump to content

Mesden

Members
  • Content Count

    102
  • Joined

  • Last visited

Community Reputation

0 Neutral

About Mesden

  • Rank
    Member
  1. I'm having a problem with my website and Table Data. I'll start with this page: (http://www.livefix.net/services.php). On a normal view it looks perfectly fine, but if I were to zoom in by scrolling the mouse, it makes the content lose it's formatting. What can I do with the coding so that the data stays exactly in place, regardless of whether or not I zoom in/out? The service pages linked up to the main one are doing the exact same thing. I'm not that smart when it comes to HTML coding.
  2. The detected DOCTYPE Declaration "<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">" has been suppressed and the DOCTYPE for "XHTML 1.0 Transitional" inserted instead, but even if no errors are shown below the document will not be Valid until you update it to reflect this new DOCTYPE.This is what I've got at the top:<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">Tried reverting it back to what it was originally but it still told me the XHTML wasn't valid.
  3. I'm looking for something like this:http://www.extremeease.com/helpconsole_2010_download.htmI just don't want to have to pay money for it. Anything out there?
  4. Mesden

    E-mail Webform

    Those links look latin to me lolIs there anything a bit more simplified?
  5. Mesden

    E-mail Webform

    Can someone tell me specifically what I would need to add to this webform to be able to have the information E-mailed to me?Thanks <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>Application for Expert Status</title><link rel="stylesheet" type="text/css" href="view.css" media="all"><script type="text/javascript" src="view.js"></script></head><body id="main_body" > <img id="top" src="top.png" alt=""> <div id="form_container"> <h1><a>Application for Expert Status</a></h1> <form id="form_134652" class="appnitro" enctype="multipart/form-data" method="post" action=""> <div class="form_description"> <h2>Application for Expert Status</h2> <p></p> </div> <ul > <li id="li_1" > <label class="description" for="element_1">Full Name: </label> <div> <input id="element_1" name="element_1" class="element text medium" type="text" maxlength="255" value=""/> </div> </li> <li id="li_2" > <label class="description" for="element_2">E-mail Address: </label> <div> <input id="element_2" name="element_2" class="element text medium" type="text" maxlength="255" value=""/> </div> </li> <li id="li_3" > <label class="description" for="element_3">Example Submission: </label> <div> <textarea id="element_3" name="element_3" class="element textarea medium"></textarea> </div><p class="guidelines" id="guide_3"><small>We want to see your your knowledge in action. Describe a scenario which relates to the role you are applying to, as well as a resolution of resolving that problem.</small></p> </li> <li id="li_6" > <label class="description" for="element_6">Education: </label> <span> <input id="element_6_1" name="element_6_1" class="element checkbox" type="checkbox" value="1" /><label class="choice" for="element_6_1">CompTIA A+ Certication</label><input id="element_6_2" name="element_6_2" class="element checkbox" type="checkbox" value="1" /><label class="choice" for="element_6_2">University Degree (Bachelor's, Master's Etc.)</label><input id="element_6_3" name="element_6_3" class="element checkbox" type="checkbox" value="1" /><label class="choice" for="element_6_3">High School Graduate</label><input id="element_6_4" name="element_6_4" class="element checkbox" type="checkbox" value="1" /><label class="choice" for="element_6_4">Other / Unlisted Certification</label> </span> </li> <li id="li_7" > <label class="description" for="element_7">Years of Experience: </label> <div> <select class="element select medium" id="element_7" name="element_7"> <option value="" selected="selected"></option><option value="1" >< 6 Months</option><option value="2" >1-2 Years</option><option value="3" >3-5 Years</option><option value="4" >6-8 Years</option><option value="5" >> 9 Years</option><option value="6" >No Experience</option> </select> </div><p class="guidelines" id="guide_7"><small>Years of experience related to the role in which you are applying to.</small></p> </li> <li id="li_4" > <label class="description" for="element_4">Additional Comments related to your Education / Years of Experience: </label> <div> <textarea id="element_4" name="element_4" class="element textarea medium"></textarea> </div> </li> <li id="li_5" > <label class="description" for="element_5">Upload a Resume or Cover Letter: </label> <div> <input id="element_5" name="element_5" class="element file" type="file"/> </div> </li> <li class="buttons"> <input type="hidden" name="form_id" value="134652" /> <input id="saveForm" class="button_text" type="submit" name="submit" value="Submit" /> </li> </ul> </form> <div id="footer"> Generated by <a href="http://www.phpform.org">pForm</a> </div> </div> <img id="bottom" src="bottom.png" alt=""> </body></html>
  6. This one? <?phprequire 'upload_functions.php';require 'upload_file_settings.php';$MAX_FILE_SIZE = 1000000;if (isset($_FILES['userfile'])) { $error = $_FILES['userfile']['error']; $type = $_FILES['userfile']['type']; $name = $_FILES['userfile']['name']; $tmpname = $_FILES['userfile']['tmp_name']; $size = $_FILES['userfile']['size']; $err = false; if ($size >= $MAX_FILE_SIZE) { $errmsg = "Error: File is too large. Replay File must be below 1MB.<br />"; $err = true; } if ($error == UPLOAD_ERR_PARTIAL) { $errmsg = "Error: The upload was not completed successfully. Please try again.<br />"; $err = true; } if ($error == UPLOAD_ERR_NO_FILE) { $errmsg = "Error: Please select a Replay to upload.<br />"; $err = true; } if (!is_uploaded_file($tmpname)) { $errmsg = "Error: Uploaded filename doesn't point to an uploaded file.<br />"; $err = true; } if (file_exists("./replay_files/" . $_FILES["userfile"]["name"])) { $newfilename = renameFile("./replay_files/", $_FILES["userfile"]["name"]); if (!$newfilename) { $errmsg = "Error: File already exists, unable to rename. Rename and try again."; $err = true; } else { $_FILES["userfile"]["name"] = $newfilename; $name = $newfilename; } } if ($err !== true) { if (class_exists("MPQFile") || (include 'mpqfile.php')) { $start = microtime_float(); if ($_POST['debug'] == 1) { echo sprintf("<b>Debugging is on.</b><br />\n"); } $a = new MPQFile($tmpname,true,(($_POST['debug'] == 1)?2:0)); $init = $a->getState(); if ($init == MPQ_ERR_NOTMPQFILE) echo "Could not parse the Replay. Check to make sure it's a valid .SC2Replay File.<br />\n"; else if ($a->getVersion() < 9) echo "Error: Invalid Version.<br />\n"; else { // file is valid, save it move_uploaded_file($_FILES["userfile"]["tmp_name"],"./replay_files/" . $_FILES["userfile"]["name"]); // update database //echo 'file uploaded'; $datetime = date('Y-m-d H:i:s'); $replay_query = "INSERT INTO `replays` (`id`, `description`, `file_name`, `datetime`) VALUES (NULL, '".$_POST['textarea']."','".$_FILES["userfile"]["name"]."','".$datetime."')"; $insert = mysql_query($replay_query, $con) or die('could not insert replay file.'); $insert_id = mysql_insert_id(); if ($insert_id) { $major_version = $a->getVersion(); $build = $a->getBuild(); $b = $a->parseReplay(); $map = $b->getMapName(); $game_length = $b->getFormattedGameLength(); $team_size = $b->getTeamSize(); $game_speed = $b->getGameSpeed(); //$game_speed = $b->getGameSpeedText(); // Read and save game data $game_query = "INSERT INTO `games` (`id` , `replay_id`, `major_version` ,`build` ,`map` ,`game_length` ,`team_size` , `game_speed`) VALUES (NULL , '$insert_id', '$major_version', '$build', '$map', '$game_length', '$team_size', '$game_speed')"; $insert_game = mysql_query($game_query, $con) or die('Could not insert game record.'); //Read and save player data $tmp = $b->getPlayers(); foreach($tmp as $value) { $team = ($value['party'] > 0)?"Team ".$value['party']:"-"; $apm_avg = ($value['party'] > 0)?(round($value['apmtotal'] / ($b->getGameLength() / 60))):0; $winner = (isset($value['won']))?$value['won']:(($value['party'] > 0)?"Unknown":"-"); $player_query = "INSERT INTO `players` (`id` ,`replay_id` , `name` ,`long_name` ,`race` ,`color` ,`team` ,`average_apm` , `winner`) VALUES (NULL , '$insert_id', '".$value['sName']."', '".$value['lName']."', '".$value['race']."', '".$value['color']."', '".$team."', '$apm_avg', '$winner')"; $insert_player = mysql_query($player_query, $con) or die('could not insert player record'); if ($value['party'] > 0) { $apmFileName = './apm_in/'.$value['id']."_".md5($name).".png"; $arAPMfilenames[] = $apmFileName; createAPMImage($value['apm'],$b->getGameLength(),$apmFileName, $value['sName']); } } mergeAPMFiles($arAPMfilenames, $name); } $messages = $b->getMessages(); if (count($messages) > 0) { $data = "<b>Messages:</b><br /><table border=\"1\"><tr><th>Time</th><th>Player</th><th>Target</th><th>Message</th></tr>\n"; foreach ($messages as $val) $data .= sprintf("<tr><td>%d sec</td><td>%s</td><td>%s</td><td>%s</td></tr>\n",$val['time'], $val['name'], ($val['target'] == 2)?"Alliance":"All",$val['message']); $data .= "</table><br />\n"; } $t = $b->getEvents(); if (isset($sc2_abilityCodes) || (include 'abilitycodes.php')) { $data .= "<b>Production Chart:</b> <br /><table border=\"1\"><tr><th>Timecode</th>\n"; $pNum = count($tmp); foreach ($tmp as $value) { if ($value['party'] > 0) $data .= sprintf("<th>%s (%s)</th>",$value['sName'],$value['race']); } $data .= "</tr>\n"; foreach ($t as $value) { $data .= sprintf("<tr><td>%d sec</td>",$value['t'] / 16); foreach ($tmp as $value2) { if ($value2['party'] == 0) continue; if ($value['p'] == $value2['id']) $data .= sprintf("<td>%s</td>",$b->getAbilityString($value['a'])); else $data .= "<td></td>"; } $data .= "</tr>\n"; } $data .= "</table>"; $file=fopen("logs/".$name.".xls","w"); fputs($file,$data); fclose($file); } } } header('location: show_replay/show.php?id='.$insert_id); exit(); } else { mysql_close($con); header('location: upload_file.php?error='.urlencode($errmsg)); exit(); }}mysql_close($con);?>
  7. SC2REPLAY.PHP (NEW)<?php/* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.*/class SC2Replay { public static $gameSpeeds = array(0 => "Slower", 1=> "Slow", 2=> "Normal", 3=> "Fast", 4=> "Faster"); public static $difficultyLevels = array(0 => "Very easy", 1=> "Easy", 2=> "Medium", 3=> "Hard", 4=> "Very Hard", 5 => "Insane"); public static $gameSpeedCE = array(0 => 39, 1=> 44, 2=> 60, 3=> 64, 4=> 64); // estimates, weird values public static $colorIndices = array(1 => "Red", 2=> "Blue", 3=> "Teal", 4=> "Purple", 5=> "Yellow", 6 => "Orange", 7=> "Green", 8=> "Pink"); private $players; //array, indices: color, team, sname, lname, race, startRace, handicap, ptype private $gameLength; // game length in seconds private $mapName; private $gameSpeed; // game speed, number from 0-4. see $gameSpeeds array above private $teamSize; // team size in the format xvx, eg. 1v1 private $gamePublic; private $realm; private $version; private $build; private $events; // contains an array of the events in replay.game.events file private $debug; // debug, currently true or false private $debugNewline; // contents are appended to the end of all debug messages private $messages; // contains an array of the chat log messages private $winnerKnown; private $unitsDict; function __construct() { $this->players = array(); $this->gameLength = 0; $this->mapName = NULL; $this->gameSpeed = 0; $this->teamSize = NULL; $this->debug = false; $this->debugNewline = "<br />\n"; $this->winnerKnown = false; $this->unitsDict = array(); } // parameter needs to be an instance of MPQFile function parseReplay($mpqfile) { if (!($mpqfile instanceof MPQFile) || !$mpqfile->isParsed()) return false; // include utility class if it is available and not loaded already if (!class_exists('SC2ReplayUtils') && (file_exists('sc2replayutils.php'))) { include 'sc2replayutils.php'; } $this->gameLength = $mpqfile->getGameLength(); $this->version = $mpqfile->getVersion(); $this->build = $mpqfile->getBuild(); // then parse replay.details file $file = $mpqfile->readFile("replay.details"); $start = microtime_float(); if ($file !== false) { $this->parseDetailsFile($file); } else if ($this->debug) $this->debug("Error reading the replay.details file"); if ($this->debug) $this->debug(sprintf("Parsed replay.details file in %d ms.",(microtime_float() - $start)*1000)); $file = $mpqfile->readFile("replay.initData"); $start = microtime_float(); if ($file !== false) { $this->parseInitDataFile($file); } else if ($this->debug) $this->debug("Error reading the replay.initData file"); if ($this->debug) $this->debug(sprintf("Parsed replay.initData file in %d ms.",(microtime_float() - $start)*1000)); $file = $mpqfile->readFile("replay.attributes.events"); $start = microtime_float(); if ($file !== false) { $this->parseAttributesFile($file); } else if ($this->debug) $this->debug("Error reading the replay.attributes.events file"); if ($this->debug) $this->debug(sprintf("Parsed replay.attributes.events file in %d ms.",(microtime_float() - $start)*1000)); $num = 0; $file = $mpqfile->readFile("replay.game.events"); $start = microtime_float(); if ($file !== false) $num = $this->parseGameEventsFile($file); else if ($this->debug) $this->debug("Error reading the replay.game.events file"); if ($this->debug) $this->debug(sprintf("Parsed replay.game.events file in %d ms, found $num events.",(microtime_float() - $start)*1000)); $file = $mpqfile->readFile("replay.message.events"); $start = microtime_float(); if ($file !== false) $this->parseChatLog($file); else if ($this->debug) $this->debug("Error reading the replay.message.events file"); if ($this->debug) $this->debug(sprintf("Parsed replay.message.events file in %d ms.",(microtime_float() - $start)*1000)); } private function debug($message) { echo $message.($this->debugNewline); } function setDebugNewline($str) { $this->debugNewline = $str; } function setDebug($num) { $this->debug = $num; } function isWinnerKnown() { return $this->winnerKnown; } function getPlayers() { return $this->players; } function getMapName() { return $this->mapName; } function getGameSpeed() { return $this->gameSpeed; } function getGameSpeedText() { return self::$gameSpeeds[$this->gameSpeed]; } function getTeamSize() { return $this->teamSize; } function getVersion() { return $this->version; } function getBuild() { return $this->build; } function getMessages() { return $this->messages; } function getRealm() { return $this->realm; } // getFormattedGameLength returns the time in h hrs, m mins, s secs function getFormattedGameLength() { return $this->getFormattedSecs($this->gameLength); } function getFormattedSecs($secs) { $o = ""; $hrs = floor($secs / 3600); $mins = floor($secs / 60) % 60; $secs = $secs % 60; if ($hrs > 0) $o = "$hrs hrs, "; if ($mins > 0) $o .= "$mins mins, "; $o .= "$secs secs"; return $o; } function getUnits() { return $this->unitsDict; } function getEvents() { return $this->events; } function getGameLength() { return $this->gameLength; } // parse replay.initData file function parseInitDataFile($string) { $numByte = 0; $numPlayers = MPQFile::readByte($string,$numByte); $nullName = false; $playerNames = array(); foreach ($this->players as $player) $playerNames[] = $player['name']; $tmpArray = array(); for ($i = 1;$i <= $numPlayers;$i++) { $nickLen = MPQFile::readByte($string,$numByte); if ($nickLen > 0) { $name = MPQFile::readBytes($string,$numByte,$nickLen); $tmpArray[$i] = array( "name" => $name, "isObs" => TRUE, "id" => $i, "isComp" => FALSE, "team" => 0 ); // set initial values $numByte += 5; } else { if (!$nullName) { $nullName = true; $numByte--; } $numByte += 5; } } $numByte += 6; // skip 6 bytes, fixed length, unknown what it means $numByte += 4; // skip literal string 'Dflt' $numByte += 15; // skip unknown 15 bytes $accountIdentifierLength = MPQFile::readByte($string,$numByte); // present at least in My Documents\Starcraft 2\Accounts\<value> if ($accountIdentifierLength > 0) $accountIdentifier = MPQFile::readBytes($string,$numByte,$accountIdentifierLength); $numByte += 684; // length seems to be fixed, data seems to vary at least based on number of players while (true) { $str = MPQFile::readBytes($string,$numByte,4); if ($str != 's2ma') { $numByte -= 4; break; } $numByte += 2; // 0x00 0x00 $realm = MPQFile::readBytes($string,$numByte,2); $this->realm = $realm; $depHash = unpack("H*", MPQFile::readBytes($string,$numByte,32)); $depHash = $depHash[1]; $str = "Unknown"; if ((class_exists("SC2ReplayUtils") || (include 'sc2replayutils.php')) && isset(SC2ReplayUtils::$depHashes[$depHash])) { $str = SC2ReplayUtils::$depHashes[$depHash]['name']; if (SC2ReplayUtils::$depHashes[$depHash]['type'] == SC2_DEPMAP) $this->mapName = $str; } if ($this->debug) $this->debug(sprintf("Got debug hash $depHash (%s)",$str)); } // start of variable length data portion $numByte += 2; $numPlayers = MPQFile::readByte($string,$numByte); $i = $numPlayers; foreach ($tmpArray as $player) { if (in_array($player['name'],$playerNames)) continue; $player['id'] = $i; $this->players[$i] = $player; $i--; } $numByte += 4; // player-specific data starts } // parse replay.details file and add parsed stuff to the object // $string contains the contents of the file function parseDetailsFile($string) { if ($this->debug) $this->debug("Parsing replay.details file..."); $numByte = 0; $numByte += 6; $numPlayers = MPQFile::readByte($string,$numByte) / 2; for ($i = 1; $i <= $numPlayers;$i++) { $p = $this->parsePlayerStruct($string,$numByte,$i); } $mapnameLen = MPQFile::readByte($string,$numByte) / 2; $mapName = MPQFile::readBytes($string,$numByte,$mapnameLen); $this->mapName = $mapName; $numByte += 2; // 04 02 $u1Len = MPQFile::readByte($string,$numByte) / 2; if ($u1Len > 0) MPQFile::readByte($string,$numByte,$u1Len); $numByte += 5; // 06 05 02 00 02 $minimapnameLen = MPQFile::readByte($string,$numByte) / 2; $minimapName = MPQFile::readBytes($string,$numByte,$minimapnameLen); } // parse a player struct in the replay.details file private function parsePlayerStruct($string,&$numByte,$id) { $numByte += 4; $sNameLen = MPQFile::readByte($string,$numByte) / 2; if ($sNameLen > 0) $sName = MPQFile::readBytes($string,$numByte,$sNameLen); else $sName = NULL; $numByte += 5; // 02 05 08 00 09 $numByte += 4; // 00/04 02 07 00 $numByte += 3; // 00 00 00 // 00 53 32 (S2) $hadKey = true; $keys = array(); while ($hadKey) { $hadKey = false; $key = unpack("c2",MPQFile::readBytes($string,$numByte,2)); if ($key[2] == 9) { $hadKey = true; $keys[$key[1]] = $this->parseKeyVal($string,$numByte); } else if ($key[1] == 4 && $key[2] == 2) { break; } } if ($this->debug) { foreach ($keys as $k => $v) $this->debug("Got pre-race($sName) key: $k, value: $v"); } $raceLen = MPQFile::readByte($string,$numByte) / 2; if ($raceLen > 0) $race = MPQFile::readBytes($string,$numByte,$raceLen); else $race = NULL; $numByte += 3; // 06 05 08 $hadKey = true; while ($hadKey) { $keyVal = ""; $hadKey = false; $key = unpack("c2",MPQFile::readBytes($string,$numByte,2)); if ($key[2] == 9) { $hadKey = true; $keyVal = $this->parseKeyVal($string,$numByte); if ($key[1] == 2) { $cR = $keyVal / 2; } // red color if ($key[1] == 4) { $cG = $keyVal / 2; } // green color if ($key[1] == 6) { $cB = $keyVal / 2; } // blue color if ($key[1] == 16) { $party = $keyVal / 2; } // party number? if ($this->debug) $this->debug(sprintf("%s Key: %d, value: %d",$sName,$key[1], $keyVal)); } else if ($key[1] == 5 && $key[2] == 18) {$numByte -= 2; break; } // next player else if ($key[1] == 2 && $key[2] == 2) { break; } // end of player section } if (($sName === NULL)) { if ($this->debug) $this->debug("Got null player"); return; } // $this->players[$id]["sName"] = $sName; // deprecated array value before there was only a short name $this->players[$id]["name"] = $sName; // player name $this->players[$id]["lrace"] = $race; // locale-specific player race $this->players[$id]["race"] = ""; // player race in english, populated by checking which workers they build $this->players[$id]["id"] = $id; $this->players[$id]["party"] = $party; $this->players[$id]["team"] = 0; $this->players[$id]["color"] = sprintf("%02X%02X%02X",$cR,$cG,$cB); $this->players[$id]["apmtotal"] = 0; $this->players[$id]["apm"] = array(); $this->players[$id]["firstevents"] = array(); $this->players[$id]["numevents"] = array(); $this->players[$id]["ptype"] = ""; $this->players[$id]["handicap"] = 0; $this->players[$id]["isComp"] = false; $this->players[$id]["uid"] = $keys[8] / 2; $this->players[$id]["isObs"] = false; // all players present in replay.details file are not observers if ($this->debug) $this->debug(sprintf("Got player: %s, Race: %s, Party: %s, Color: %s",$sName, $race, $party, $this->players[$id]["color"])); return; } // parameter is the contents of the replay.attributes.events file private function parseAttributesFile($string) { if ($this->debug) $this->debug("Parsing replay.attributes.events file"); $numByte = 4; // skip the 4-byte header $numAttribs = MPQFile::readUInt32($string,$numByte); $attribArray = array(); for ($i = 0;$i < $numAttribs;$i++) { $attribHeader = MPQFile::readUInt32($string,$numByte); $attributeId = MPQFile::readUInt32($string,$numByte); $playerId = MPQFile::readByte($string,$numByte); $attribVal = ""; // values are stored in reverse in the file, eg Terr becomes rreT. The following loop flips the value and removes excess null bytes for ($a = 0;$a < 4;$a++) { $b = ord(substr($string,$numByte + 3 - $a)); if ($b != 0) $attribVal .= chr($; } $numByte += 4; $attribArray[$playerId][$attributeId] = $attribVal; if ($this->debug) $this->debug(sprintf("Got attrib \"%04X\" for player %d (%s), attribVal = \"%s\"", $attributeId,$playerId,(($playerId == 0x10)?"ALL":$this->players[$playerId]["name"]),$attribVal)); switch ($attributeId) { // FFA/* case 0x07D6: break; // 4v4 case 0x07D5: break; // 3v3 case 0x07D4: break; // 2v2 case 0x07D3: // my hypothesis is that every 0x07D<X> value is what the teams would // be if the game type/team size was changed. // for example if you changed the dropdownbox from 3v3 to FFA, the values // under 0x07D6 would be the initial values that you could edit. // why this is included in replay files is weird to say the least break; // 1v1 case 0x07D2: break;*/ case 0x0BBB: // handicap $this->players[$playerId]["handicap"] = $attribVal; break; case 0x0BBC: // difficulty level (of computer player, Medi for humans) switch ($attribVal) { case "Insa": $tmp = 5; break; case "VyHd": $tmp = 4; break; case "Hard": $tmp = 3; break; case "Medi": $tmp = 2; break; case "Easy": $tmp = 1; break; case "VyEy": $tmp = 0; break; default: $tmp = 2; } $this->players[$playerId]["difficulty"] = $tmp; break; case 0x0BB8: // game speed switch ($attribVal) { case "Fasr": $tmp = 4; break; case "Fast": $tmp = 3; break; case "Norm": $tmp = 2; break; case "Slow": $tmp = 1; break; case "Slor": $tmp = 0; break; default: $tmp = 2; } $this->gameSpeed = $tmp; break; case 0x01F4: // player type, Humn or Comp $this->players[$playerId]["ptype"] = $attribVal; // deprecated $this->players[$playerId]["isComp"] = ($attribVal == 'Comp')?true:false; break; case 0x0BB9: // initial race, Prot Terr Zerg or RAND $this->players[$playerId]["srace"] = $attribVal; break; case 0x07D1: // teamsizes $this->teamSize = $attribVal; break; case 0x0BC1: // game type, private(Priv)/open(Amm)? $this->gamePublic = (($attribVal == "Priv")?false:true); break; case 0x0BBA: // color index $this->players[$playerId]["colorIndex"] = intval(substr($attribVal,2)); $this->players[$playerId]["sColor"] = self::$colorIndices[intval(substr($attribVal,2))]; break; default: } } switch ($attribArray[0x10][0x07D1]) { case "1v1": $attrib = 0x07D2; break; case "2v2": $attrib = 0x07D3; break; case "3v3": $attrib = 0x07D4; break; case "4v4": $attrib = 0x07D5; break; case "FFA": $attrib = 0x07D6; break; default: if ($this->debug) $this->debug(sprintf("Unknown game mode in replay.attributes.events: %s",$attribArray[0x10][0x07D1])); return; } foreach ($attribArray as $playerId => $values) { if ($playerId == 0x10) continue; $this->players[$playerId]["team"] = intval(substr($values[$attrib],1)); } } // parse a key/value -pair struct in the replay.details file private function parseKeyVal($string, &$numByte) { $one = unpack("C",substr($string,$numByte,1)); $one = $one[1]; $retVal = $one & 0x7F; $shift = 1; $numByte++; while (($one & 0x80) > 0) { $one = unpack("C",substr($string,$numByte,1)); $one = $one[1]; $retVal = (($one & 0x7F) << $shift*7) | $retVal; $shift++; $numByte++; } return $retVal; } private function readUnitTypeID($string,&$numByte) { return ((MPQFile::readByte($string,$numByte) << 16) | (MPQFile::readByte($string,$numByte) << 8) | (MPQFile::readByte($string,$numByte))); } private function readUnitAbility($string) { $bytes = unpack("C3",substr($string,4,3)); return (($bytes[1] << 16) | ($bytes[2] << 8) | ($bytes[3])); } // gets players who actually played in the game, meaning excludes observers and party members. public function getActualPlayers() { $tmp = array(); foreach ($this->players as $val) if ($val['team'] > 0) $tmp[] = $val; return $tmp; } // parameter is the contents of the replay.message.events -file private function parseChatLog($string) { $numByte = 0; $len = strlen($string); $messages = array(); $totTime = 0; while ($numByte < $len) { $timestamp = self::parseTimeStamp($string,$numByte); $playerId = MPQFile::readByte($string,$numByte); $opcode = MPQFile::readByte($string,$numByte); $totTime += $timestamp; if ($opcode == 0x80) // header weird thingy? $numByte += 4; else if (($opcode & 0x80) == 0) { // message $messageTarget = $opcode & 3; $messageLength = MPQFile::readByte($string,$numByte); if (($opcode & 8) == 8) $messageLength += 64; if (($opcode & 16) == 16) $messageLength += 128; $message = MPQFile::readBytes($string,$numByte,$messageLength); $messages[] = array('id' => $playerId, 'name' => $this->players[$playerId]['name'], 'target' => $messageTarget, 'time' => floor($totTime / 16), 'message' => $message); } else if ($opcode == 0x83) { // ping on map? 8 bytes? $numByte += 8; } } $this->messages = $messages; } // parameter is the contents of the replay.game.events -file private function parseGameEventsFile($string) { $numByte = 0; $len = strlen($string); $playerLeft = array(); $events = array(); $time = 0; $numEvents = 0; while ($numByte < $len) { $timeStamp = self::parseTimeStamp($string,$numByte); $nextByte = MPQFile::readByte($string,$numByte); $eventType = $nextByte >> 5; // 3 lowest bits $globalEventFlag = $nextByte & 16; // 4th bit $playerId = $nextByte & 15; // bits 5-8 if (isset($this->players[$playerId])) $playerName = $this->players[$playerId]['name']; else $playerName = ""; $eventCode = MPQFile::readByte($string,$numByte); $time += $timeStamp; $numEvents++; // weird timestamp values mean that there's likely a problem with the alignment of the parse(too few/too many bytes read for an eventcode) if ($this->debug >= 2) {// if ($len - $numByte > 24) {// $bytes = unpack("C24",substr($string,$numByte,24));// $dataBytes = "";// for ($i = 1;$i <= 24;$i++) $dataBytes .= sprintf("%02X",$bytes[$i]); $this->debug(sprintf("DEBUG L2: Timestamp: %d, Frames: %d, Type: %d, Global: %d, Player ID: %d (%s), Event code: %02X Byte: %08X<br />\n", floor($time / 16),$timeStamp, $eventType, $globalEventFlag,$playerId,$playerName,$eventCode,$numByte));// } } switch ($eventType) { case 0x00: // initialization switch ($eventCode) { case 0x1B: // Player enters game case 0x0B: break; case 0x05: // game starts break; default: if ($this->debug) $this->debug(sprintf("DEBUG: Timestamp: %d, Type: %d, Global: %d, Player ID: %d (%s), Event code: %02X Byte: %08X<br />\n", $timeStamp, $eventType, $globalEventFlag,$playerId,$playerName,$eventCode,$numByte)); } break; case 0x01: // action switch ($eventCode) { case 0x09: // player quits the game if ($this->players[$playerId]['team'] > 0) // don't log observers/party members etc $playerLeft[] = $playerId; break; case 0x0B: // player uses an ability // at least 32 bytes $data = MPQFile::readBytes($string,$numByte,32); $reqTarget = unpack("C",substr($data,7,1)); $reqTarget = $reqTarget[1]; $ability = $this->readUnitAbility($data); if ($ability != 0xFFFF0F) { $events[] = array('p' => $playerId, 't' => $time, 'a' => $ability); $this->events = $events; // populate non-locale-specific race strings based on worker type if (!$this->players[$playerId]['isObs'] && $this->players[$playerId]['race'] == "") { switch ($ability) { case 0x080A00: //SCV $this->players[$playerId]['race'] = "Terran"; break; case 0x090E00: //probe $this->players[$playerId]['race'] = "Protoss"; break; case 0x0B0000: //drone $this->players[$playerId]['race'] = "Zerg"; break; } } } // at least with attack, move, right-click, if the byte after unit ability bytes is // 0x30 or 0x50, the struct takes 1 extra byte. With build orders the struct seems to be 32 bytes // and this byte is 0x00. // might also be in some other way variable-length. if ($reqTarget == 0x30) $data .= MPQFile::readByte($string,$numByte); if ($reqTarget == 0x50) $data .= MPQFile::readByte($string,$numByte); // update apm array $this->addPlayerAction($playerId, floor($time / 16)); $this->addPlayerAbility($playerId, ceil($time /16), $ability); break; case 0x2F: // player sends resources $numByte += 17; // data is 17 bytes long break; case 0x0C: // automatic update of hotkey? case 0x1C: case 0x2C: case 0x3C: // 01 01 01 01 11 01 03 02 02 38 00 01 02 3c 00 01 00 case 0x4C: // 01 02 02 01 0d 00 02 01 01 a8 00 00 01 case 0x5C: // 01 01 01 01 16 03 01 01 03 18 00 01 00 case 0x6C: // 01 04 08 01 03 00 02 01 01 34 c0 00 01 case 0x7C: // 01 05 10 01 01 10 02 01 01 1a a0 00 01 case 0x8C: case 0x9C: case 0xAC: // player changes selection $selFlags = MPQFile::readByte($string,$numByte); $dsuCount = MPQFile::readByte($string,$numByte); if($this->debug){ $this->debug("Selection Change"); $this->debug(sprintf("Player %s", $playerId)); $this->debug(sprintf("Time %d", $time)); $this->debug(sprintf("Deselected Count: %d", $dsuCount)); } $dsuExtraBits = $dsuCount % 8; $uType = array(); if ($dsuCount > 0) $dsuMap = MPQFile::readBytes($string,$numByte,floor($dsuCount / 8)); if ($dsuExtraBits != 0) { // not byte-aligned $dsuMapLastByte = MPQFile::readByte($string,$numByte); $nByte = MPQFile::readByte($string,$numByte); //Recalculating these is excessive. //ex: For extra = 2 $offsetTailMask = (0xFF >> (8-$dsuExtraBits)); //ex: 00000011 $offsetHeadMask = (~$offsetTailMask) & 0xFF; //ex: 11111100 $offsetWTailMask = 0xFF >> $dsuExtraBits; //ex: 00111111 $offsetWHeadMask = (~$offsetWTailMask) & 0xFF; //ex: 11000000 $uTypesCount = ($dsuMapLastByte & $offsetHeadMask) | ($nByte & $offsetTailMask); if($this->debug){ $this->debug(sprintf("Number of New Unit Types %d", $uTypesCount)); } for ($i = 1;$i <= $uTypesCount;$i++) { $nBytes = unpack("C3",MPQFile::readBytes($string,$numByte,3)); $byte1 = ( $nByte & $offsetHeadMask) | (($nBytes[1] & $offsetWHeadMask) >> (8 - $dsuExtraBits)); $byte2 = (($nBytes[1] & $offsetWTailMask) << $dsuExtraBits) | ( $nBytes[2] & $offsetTailMask); $byte3 = (($nBytes[2] & $offsetHeadMask) << $dsuExtraBits) | ( $nBytes[3] & $offsetTailMask); //Byte3 is almost invariably 0x01 $uType[$i]['id'] = (($byte1 << 16) | ($byte2 << 8) | $byte3) & 0xFFFFFF; $nByte = MPQFile::readByte($string,$numByte); $uType[$i]['count'] = ($nBytes[3] & $offsetHeadMask) | ($nByte & $offsetTailMask); if($this->debug){ $this->debug(sprintf(" %d x 0x%06X", $uType[$i]['count'], $uType[$i]['id'])); } } $lByte = MPQFile::readByte($string,$numByte); $totalUnits = ($nByte & $offsetHeadMask) | ($lByte & $offsetTailMask); if($this->debug){ $this->debug(sprintf("TOTAL: %d", $totalUnits)); } //Populate the unitsDict foreach($uType as $unitType){ for($i = 1; $i <= $unitType['count']; $i++){ $nBytes = unpack("C4", MPQFile::readBytes($string, $numByte,4)); $byte1 = ($lByte & $offsetHeadMask) | (($nBytes[1] & $offsetWHeadMask) >> (8 - $dsuExtraBits)); $byte2 = (($nBytes[1] & $offsetWTailMask) << $dsuExtraBits) | (($nBytes[2] & $offsetWHeadMask) >> (8 - $dsuExtraBits)); $byte3 = (($nBytes[2] & $offsetWTailMask) << $dsuExtraBits) | (($nBytes[3] & $offsetWHeadMask) >> (8 - $dsuExtraBits)); $byte4 = (($nBytes[3] & $offsetWTailMask) << $dsuExtraBits) | ( $nBytes[4] & $offsetTailMask); $uid = ($byte1 << 8) | $byte2; //Bytes 3 + 4 contain Flag Info $this->addSelectedUnit($uid, $unitType['id'], $playerId, floor($time / 16)); if($this->debug){ $this->debug(sprintf(" 0x%06X -> 0x%02X", $unitType['id'], $uid)); } $lByte = $nBytes[4]; //For looping. } } } else { // byte-aligned $uTypesCount = MPQFile::readByte($string,$numByte); if($this->debug){ $this->debug(sprintf("Number of New Unit Types %d", $uTypesCount)); } for ($i = 1;$i <= $uTypesCount;$i++) { $uType[$i]['id'] = $this->readUnitTypeID($string,$numByte); $uType[$i]['count'] = MPQFile::readByte($string,$numByte); if($this->debug){ $this->debug(sprintf(" %d x 0x%06X", $uType[$i]['count'], $uType[$i]['id'])); } } $totalUnits = MPQFile::readByte($string,$numByte); if($this->debug){ $this->debug(sprintf("TOTAL: %d", $totalUnits)); } //Populate the Units Dict foreach($uType as $unitType){ for($i = 1; $i <= $unitType['count']; $i++){ $nBytes = unpack("C4", MPQFile::readBytes($string, $numByte, 4)); $uid = ($nBytes[1] << 8) | $nBytes[2]; $this->addSelectedUnit($uid, $unitType['id'], $playerId, floor($time / 16)); if($this->debug){ $this->debug(sprintf(" 0x%06X -> 0x%02X", $unitType['id'], $uid)); } } } } //update apm fields if ($eventCode == 0xAC) { $this->addPlayerAction($playerId, floor($time / 16)); } break; case 0x0D: // manually uses hotkey case 0x1D: case 0x2D: case 0x3D: case 0x4D: case 0x5D: case 0x6D: case 0x7D: case 0x8D: case 0x9D: $byte1 = MPQFile::readByte($string,$numByte); if ($numByte < $len) { $byte2 = MPQFile::readByte($string,$numByte); $numByte--; } $extraBytes = floor($byte1 / 8); $numByte += $extraBytes; if ($byte1 & 4 && ($this->debug)) $this->debug("Found candidate hotkey event!"); if (($byte1 & 4) && (($byte2 & 6) == 6)) $numByte += 2; else if ($byte1 & 4) $numByte += 1; // update apm $this->addPlayerAction($playerId, floor($time / 16)); break; case 0x1F: // send resources case 0x2F: case 0x3F: case 0x4F: case 0x5F: case 0x6F: case 0x7F: case 0x8F: $numByte++; // 0x84 $sender = $playerId; $receiver = ($eventCode & 0xF0) >> 4; // sent minerals $bytes = MPQFile::readBytes($string,$numByte,4); $mBytes = unpack("C4",$bytes); $mineralValue = ((($mBytes[1] << 20) | ($mBytes[2] << 12) | ($mBytes[3] << 4)) >> 1) + ($mBytes[4] & 0x0F); // sent gas $bytes = MPQFile::readBytes($string,$numByte,4); $mBytes = unpack("C4",$bytes); $gasValue = ((($mBytes[1] << 20) | ($mBytes[2] << 12) | ($mBytes[3] << 4)) >> 1) + ($mBytes[4] & 0x0F); // last 8 bytes are unknown $numByte += 8; break; default: if ($this->debug) $this->debug(sprintf("DEBUG: Timestamp: %d, Type: %d, Global: %d, Player ID: %d (%s), Event code: %02X Byte: %08X<br />\n", $timeStamp, $eventType, $globalEventFlag,$playerId,$playerName,$eventCode,$numByte)); } break; case 0x02: // weird switch($eventCode) { case 0x06: $numByte += 8; // 00 00 00 04 00 00 00 04 break; case 0x07: $numByte += 4; break; default: if ($this->debug) $this->debug(sprintf("DEBUG: Timestamp: %d, Type: %d, Global: %d, Player ID: %d (%s), Event code: %02X Byte: %08X<br />\n", $timeStamp, $eventType, $globalEventFlag,$playerId,$playerName,$eventCode,$numByte)); } break; case 0x03: // replay switch ($eventCode) { case 0x87: $numByte += 8; break; case 0x01: // camera movement case 0x11: case 0x21: case 0x31: case 0x41: case 0x51: case 0x61: case 0x71: case 0x81: case 0x91: case 0xA1: case 0xB1: case 0xC1: case 0xD1: case 0xE1: case 0xF1: $numByte += 3; $nByte = MPQFile::readByte($string,$numByte); switch (($nByte & 0x70)) { case 0x10: // zoom camera up or down case 0x30: // only 0x10 matters, but due to 0x70 mask in comparison, check for this too case 0x50: $numByte++; $nByte = MPQFile::readByte($string,$numByte); case 0x20: if (($nByte & 0x20) > 0) { // zooming, if comparison is 0 max/min zoom reached $numByte++; $nByte = MPQFile::readByte($string,$numByte); } if (($nByte & 0x40) == 0) break; // if non-zero (as in 0x40), rotate segment(2 bytes) follows case 0x40: // rotate camera $numByte += 2; } break; default: if ($this->debug) $this->debug(sprintf("DEBUG: Timestamp: %d, Type: %d, Global: %d, Player ID: %d (%s), Event code: %02X Byte: %08X<br />\n", $timeStamp, $eventType, $globalEventFlag,$playerId,$playerName,$eventCode,$numByte)); } break; case 0x04: // inaction switch($eventCode) { case 0x
  8. MPQFILE.PHP (NEW)<?php/* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.*/define("MPQ_HASH_TABLE_OFFSET", 0);define("MPQ_HASH_NAME_A", 1);define("MPQ_HASH_NAME_B", 2);define("MPQ_HASH_FILE_KEY", 3);define("MPQ_HASH_ENTRY_EMPTY", (0xFFFF << 16) | 0xFFFF);define("MPQ_HASH_ENTRY_DELETED", (0xFFFF << 16) | 0xFFFE);define("MPQ_NOT_PARSED", 2);define("MPQ_PARSE_OK", 1);define("MPQ_ERR_NOTMPQFILE", -1);define("MPQ_SC2REPLAYFILE", 1);define("MPQ_UNKNOWNFILE", 0);class MPQFile { private $filename; private $fp; private $hashtable,$blocktable; private $hashTableSize, $blockTableSize; private $hashTableOffset, $blockTableOffset; private $headerOffset; private $init; private $verMajor; private $build; private $sectorSize; private $debug; private $debugNewline; private $gameLen; private $versionString; public static $cryptTable; private $fileType; private $fileData; private $archiveSize; function __construct($filename, $autoparse = true, $debug = 0) { $this->filename = $filename; $this->hashtable = NULL; $this->blocktable = NULL; $this->hashTableSize = 0; $this->blockTableSize = 0; $this->headerOffset = 0; $this->init = false; $this->verMajor = 0; $this->build = 0; $this->gameLen = 0; $this->sectorSize = 0; $this->debug = $debug; $this->debugNewline = "<br />\n"; $this->versionString = "null"; $this->fileType = MPQ_UNKNOWNFILE; if (!self::$cryptTable) self::initCryptTable(); if (file_exists($this->filename)) { $fp = fopen($this->filename, 'rb'); $contents = fread($fp, filesize($this->filename)); if ($this->debug && $contents === false) $this->debug("Error opening file $filename for reading"); if ($contents !== false) $this->fileData = $contents; fclose($fp); } if ($autoparse) $this->parseHeader(); } private function debug($message) { echo $message.($this->debugNewline); } function setDebugNewline($str) { $this->debugNewline = $str; } function setDebug($bool) { $this->debug = $bool; } static function readByte($string, &$numByte) { $tmp = unpack("C",substr($string,$numByte,1)); $numByte++; return $tmp[1]; } static function readBytes($string, &$numByte, $length) { $tmp = substr($string,$numByte,$length); $numByte += $length; return $tmp; } static function readUInt16($string, &$numByte) { $tmp = unpack("v",substr($string,$numByte,2)); $numByte += 2; return $tmp[1]; } static function readUInt32($string, &$numByte) { $tmp = unpack("V",substr($string,$numByte,4)); $numByte += 4; return $tmp[1]; } 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 = MPQ_ERR_NOTMPQFILE; 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; //fseek($fp,5,SEEK_CUR); // skip 05 08 00 02 2c //fseek($fp,24,SEEK_CUR); // skip Starcraft II replay 0x1B 0x32 0x01 0x00 //fseek($fp,25,SEEK_CUR); // skip Starcraft II replay 0x1B 0x31 0x31 0x02 0x05 0x0c //fseek($fp,30,SEEK_CUR); $fileTypeHeader = self::readUInt16($this->fileData, $fp); $fp -= 2; if ($uDataSize == 0 || $fileTypeHeader != 0x0805) { // file is not replay file, so skip following section if ($this->debug) $this->debug(sprintf("Unknown user data block(%04X), skipping...",$fileTypeHeader)); $fp = $headerOffset; continue; } if ($this->debug) $this->debug(sprintf("File seems to be a Starcraft 2 replay file.")); $this->fileType = MPQ_SC2REPLAYFILE; $fp += 30; $loop = 0; $versiontemp1 = 0; $versiontemp2 = 0; while ($fp < ($uDataSize + $uDataStart)) { $key = unpack("C2",self::readBytes($this->fileData,$fp,2)); $value = $this->parseKeyVal($this->fileData,$fp); if ($this->debug) $this->debug(sprintf("User header key: %02X %02X value: %d",$key[1],$key[2],$value)); if ($loop == 0) { switch ($key[1]) { case 0x02: // major version $this->verMajor = $value / 2; break; case 0x04: // minor version $versiontemp1 = $value / 2; break; case 0x06: // minorer? version $versiontemp2 = $value / 2; break; case 0x08; $this->build = $value / 2; $loop++; break; default: } } if ($loop == 1) { switch ($key[1]) { case 0x06: $this->gameLen = ceil($value / 32); break; default: } } } $this->versionString = sprintf("%d.%d.%d.%d",$this->verMajor,$versiontemp1,$versiontemp2,$this->build); /* $verMajor = $this->readUInt16(); $this->verMajor = $verMajor; $build = $this->readUInt32(true); $this->build = $build; $build2 = $this->readUInt32(true); fseek($fp,2,SEEK_CUR); // skip 02 00 $gameLen = $this->readUInt16(true) / 2; $this->gameLen = $gameLen; */ $fp = $headerOffset; } else if ($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)", 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)", 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 = MPQ_PARSE_OK; return true; } function getFileSize($filename) { if ($this->init !== MPQ_PARSE_OK) { if ($this->debug) $this->debug("Tried to use getFileSize without initializing"); return false; } $hashA = self::hashStuff($filename, MPQ_HASH_NAME_A); $hashB = self::hashStuff($filename, MPQ_HASH_NAME_; $hashStart = self::hashStuff($filename, MPQ_HASH_TABLE_OFFSET) & ($this->hashTableSize - 1); $tmp = $hashStart; do { if (($this->hashtable[$tmp*4 + 3] == MPQ_HASH_ENTRY_DELETED) || ($this->hashtable[$tmp*4 + 3] == MPQ_HASH_ENTRY_EMPTY)) return false; if (($this->hashtable[$tmp*4] == $hashA) && ($this->hashtable[$tmp*4 + 1] == $hashB)) { // found file $blockIndex = ($this->hashtable[($tmp *4) + 3]) *4; $fileSize = $this->blocktable[$blockIndex + 2]; return $fileSize; } $tmp = ($tmp + 1) % $this->hashTableSize; } while ($tmp != $hashStart); if ($this->debug) $this->debug("Did not find file $filename in archive"); return false; } function readFile($filename) { if ($this->init !== MPQ_PARSE_OK) { if ($this->debug) $this->debug("Tried to use getFile without initializing"); return false; } $hashA = self::hashStuff($filename, MPQ_HASH_NAME_A); $hashB = self::hashStuff($filename, MPQ_HASH_NAME_; $hashStart = self::hashStuff($filename, MPQ_HASH_TABLE_OFFSET) & ($this->hashTableSize - 1); $tmp = $hashStart; $blockSize = -1; do { if (($this->hashtable[$tmp*4 + 3] == MPQ_HASH_ENTRY_DELETED) || ($this->hashtable[$tmp*4 + 3] == MPQ_HASH_ENTRY_EMPTY)) return false; if (($this->hashtable[$tmp*4] == $hashA) && ($this->hashtable[$tmp*4 + 1] == $hashB)) { // found file $blockIndex = ($this->hashtable[($tmp *4) + 3]) *4; $blockOffset = $this->blocktable[$blockIndex] + $this->headerOffset; $blockSize = $this->blocktable[$blockIndex + 1]; $fileSize = $this->blocktable[$blockIndex + 2]; $flags = $this->blocktable[$blockIndex + 3]; break; } $tmp = ($tmp + 1) % $this->hashTableSize; } while ($tmp != $hashStart); if ($blockSize == -1) { if ($this->debug) $this->debug("Did not find file $filename in archive"); return false; } $flag_file = $flags & 0x80000000; $flag_checksums = $flags & 0x04000000; $flag_deleted = $flags & 0x02000000; $flag_singleunit = $flags & 0x01000000; $flag_hEncrypted = $flags & 0x00020000; $flag_encrypted = $flags & 0x00010000; $flag_compressed = $flags & 0x00000200; $flag_imploded = $flags & 0x00000100; if ($this->debug) $this->debug(sprintf("Found file $filename with flags %08X, block offset %08X, block size %d and file size %d", $flags, $blockOffset,$blockSize,$fileSize)); if (!$flag_file) return false; $fp = $blockOffset; if ($flag_checksums) { for ($i = $fileSize;$i > 0;$i -= $this->sectorSize) { $sectors[] = self::readUInt32($this->fileData, $fp); $blockSize -= 4; } $sectors[] = self::readUInt32($this->fileData, $fp); $blockSize -= 4; } else { $sectors[] = 0; $sectors[] = $blockSize; } $c = count($sectors) - 1; $totDur = 0; $output = ""; for ($i = 0;$i < $c;$i++) { $sectorLen = $sectors[$i + 1] - $sectors[$i]; if ($sectorLen == 0) break; $fp = $blockOffset + $sectors[$i]; $sectorData = self::readBytes($this->fileData, $fp,$sectorLen); if ($this->debug) $this->debug(sprintf("Got %d bytes of sector data",strlen($sectorData))); if ($flag_compressed && (($flag_singleunit && ($blockSize < $fileSize)) || ($flag_checksums && ($sectorLen < $this->sectorSize)))) { $numByte = 0; $compressionType = self::readByte($sectorData,$numByte); $sectorData = substr($sectorData,1); switch ($compressionType) { case 0x02: if ($this->debug) $this->debug("Compression type: gzlib"); $output .= self::deflate_decompress($sectorData); break; case 0x10: if ($this->debug) $this->debug("Compression type: bzip2"); $output .= self::bzip2_decompress($sectorData); break; default: if ($this->debug) $this->debug(sprintf("Unknown compression type: %d",$compressionType)); return false; } } else $output .= $sectorData; } if (strlen($output) != $fileSize) { if ($this->debug) $this->debug(sprintf("Decrypted/uncompressed file size(%d) does not match original file size(%d)", strlen($output),$fileSize)); return false; } return $output; } function parseReplay() { if ($this->init !== MPQ_PARSE_OK) { if ($this->debug) $this->debug("Tried to use parseReplay without initializing"); return false; } if (class_exists("SC2Replay") || (include 'sc2replay.php')) { $tmp = new SC2Replay(); if ($this->debug) $tmp->setDebug($this->debug); $tmp->parseReplay($this); return $tmp; } else { if ($this->debug) $this->debug("Unable to find or load class SC2Replay"); return false; } } function isParsed() { return $this->init === MPQ_PARSE_OK; } function getState() { return $this->init; } function getFileType() { return $this->fileType; } function getBuild() { return $this->build; } function getVersion() { return $this->verMajor; } function getVersionString() { return $this->versionString; } function getHashTable() { return $this->hashtable; } function getBlockTable() { return $this->blocktable; } function getGameLength() { return $this->gameLen; } // prints block table or hash table, $data is the data in an array of UInt32s function printTable($data) { $this->debug("Hash table: HashA, HashB, Language+platform, Fileblockindex"); $this->debug("Block table: Offset, Blocksize, Filesize, flags"); $entries = count($data) / 4; $tmpnewline = $this->debugNewline; $this->debugNewline = ""; for ($i = 0;$i < $entries;$i++) { $blockIndex = $i * 4; $blockOffset = $data[$blockIndex] + $this->headerOffset; $blockSize = $data[$blockIndex + 1]; $fileSize = $data[$blockIndex + 2]; $flags = $data[$blockIndex + 3]; $this->debug(sprintf("<pre>%08X %08X %08X %08X</pre>",$blockOffset, $blockSize, $fileSize, $flags)); } $this->debugNewline = $tmpnewline; } // the following replaces a file in the archive, meaning a file with that filename must be present already. function replaceFile($filename, $filedata) { if ($this->getFileSize($filename) === false || strlen($filedata) == 0) return false; if ($this->init !== MPQ_PARSE_OK) { if ($this->debug) $this->debug("Tried to use replaceFile without initializing"); return false; } $hashA = self::hashStuff($filename, MPQ_HASH_NAME_A); $hashB = self::hashStuff($filename, MPQ_HASH_NAME_; $hashStart = self::hashStuff($filename, MPQ_HASH_TABLE_OFFSET) & ($this->hashTableSize - 1); $tmp = $hashStart; $blockIndex = -1; do { if (($this->hashtable[$tmp*4 + 3] == MPQ_HASH_ENTRY_DELETED) || ($this->hashtable[$tmp*4 + 3] == MPQ_HASH_ENTRY_EMPTY)) return false; if (($this->hashtable[$tmp*4] == $hashA) && ($this->hashtable[$tmp*4 + 1] == $hashB)) { // found file $blockIndex = ($this->hashtable[($tmp *4) + 3]) *4; $blockOffset = $this->blocktable[$blockIndex] + $this->headerOffset; $blockSize = $this->blocktable[$blockIndex + 1]; $fileSize = $this->blocktable[$blockIndex + 2]; $flags = $this->blocktable[$blockIndex + 3]; break; } $tmp = ($tmp + 1) % $this->hashTableSize; } while ($tmp != $hashStart); if ($blockIndex == -1) return false; // fix block table offsets for ($i = 0;$i < $this->blockTableSize;$i++) { if ($i == $blockIndex) continue; if ($this->blocktable[$i*4] > ($blockOffset - $this->headerOffset)) $this->blocktable[$i*4] -= $blockSize; } if ($this->blockTableOffset > $blockOffset) { $this->blockTableOffset = $this->blockTableOffset - $this->headerOffset - $blockSize; $this->fileData = substr_replace($this->fileData, pack("V",$this->blockTableOffset),$this->headerOffset + 20,4); $this->blockTableOffset += $this->headerOffset; } if ($this->hashTableOffset > $blockOffset) { $this->hashTableOffset = $this->hashTableOffset - $this->headerOffset - $blockSize; $this->fileData = substr_replace($this->fileData, pack("V",$this->hashTableOffset),$this->headerOffset + 16,4); $this->hashTableOffset += $this->headerOffset; } // remove the original file contents $this->fileData = substr_replace($this->fileData,'',$blockOffset,$blockSize); //$this->fileData = substr_replace($this->fileData,str_repeat(chr(0),$blockSize),$blockOffset,$blockSize); $newFileSize = strlen($filedata); // attempt to use bzip2 compression $compressedData = chr(16) . bzcompress($filedata); //$compressedData = $filedata; $newBlockOffset = strlen($this->fileData) - $this->headerOffset; if (strlen($compressedData) >= $newFileSize) { $newFlags = (0x40000000 << 1) | 0x01000000; //$newFlags = 0x81000000; $compressedData = $filedata; $newBlockSize = $newFileSize; } else { $newFlags = (0x40000000 << 1) | 0x01000200; //$newFlags = 0x81000200; $newBlockSize = strlen($compressedData); } // fix archive size $this->fileData = substr_replace($this->fileData, pack("V",$this->archiveSize + $newBlockSize), $this->headerOffset + 8, 4); // populate variables $this->fileData .= $compressedData; $this->printTable($this->blocktable); $this->blocktable[$blockIndex] = $newBlockOffset; $this->blocktable[$blockIndex + 1] = $newBlockSize; $this->blocktable[$blockIndex + 2] = $newFileSize; $this->blocktable[$blockIndex + 3] = $newFlags; // encrypt the block table $resultBlockTable = self::encryptStuff($this->blocktable,self::hashStuff("(block table)", MPQ_HASH_FILE_KEY)); // replace the block table in fileData variable for ($i = 0;$i < $this->blockTableSize * 4;$i++) { //$this->fileData = substr($this->fileData, 0, $this->blockTableOffset + $i * 4) . pack("V",$resultBlockTable[$i]) . substr($this->fileData, $this->blockTableOffset + $i * 4 + 4); //$this->fileData = substr_replace($this->fileData, pack("v",$resultBlockTable[$i] & 0xFFFF), $this->blockTableOffset + $i * 4, 2); //$this->fileData = substr_replace($this->fileData, pack("v",($resultBlockTable[$i] >> 16) & 0xFFFF), $this->blockTableOffset + $i * 4 + 2, 2); $this->fileData = substr_replace($this->fileData, pack("V",$resultBlockTable[$i]), $this->blockTableOffset + $i * 4, 4); } return true; } // saves the mpq data as a file. function saveAs($filename, $overwrite = false) { if (file_exists($filename) && !$overwrite) return false; $fp = fopen($filename, "wb"); if (!$fp) return false; $result = fwrite($fp,$this->fileData); if (!$result) return false; fclose($fp); return true; } function insertChatLogMessage($newMessage, $player, $time) { if ($this->init !== MPQ_PARSE_OK || $this->getFileSize("replay.message.events") == 0) return false; if (!is_numeric($player)) return false; //$playerId = $this->addFakeObserver($player); else $playerId = $player; if ($playerId <= 0) return false; $string = $this->readFile("replay.message.events"); $numByte = 0; $time = $time * 16; $fileSize = strlen($string); $messageSize = strlen($newMessage); if ($messageSize >= 256) return false; $totTime = 0; while ($numByte < $fileSize) { $pastHeaders = true; $start = $numByte; $timestamp = SC2Replay::parseTimeStamp($string,$numByte); $pid = self::readByte($string,$numByte); $opcode = self::readByte($string,$numByte); $totTime += $timestamp; if ($opcode == 0x80) { $numByte += 4; $pastHeaders = false; } else if (($opcode & 0x80) == 0) { // message $messageTarget = $opcode & 3; $messageLength = self::readByte($string,$numByte); if (($opcode & 8) == 8) $messageLength += 64; if (($opcode & 16) == 16) $messageLength += 128; $message = self::readBytes($string,$numByte,$messageLength); } else if ($opcode == 0x83) { // ping on map? 8 bytes? $numByte += 8; } if ($pastHeaders && ($totTime >= $time)) { $opcode = 0; if ($messageSize >= 128) { $opcode = $opcode | 16; $messageSize -= 128; } if ($messageSize >= 64) { $opcode = $opcode | 8; $messageSize -= 64; } break; } } $messageString = pack("c4", 4, $playerId, $opcode, $messageSize). $newMessage; $newData = substr_replace($string, $messageString, $start, 0); $this->replaceFile("replay.message.events", $newData); return true; } // $obsName is the fake observer name, $string is the contents of replay.initData file // DOES NOT WORK CURRENTLY! function addFakeObserver($obsName) { return false; // this function does not work currently so DO NOT USE! if ($this->init !== MPQ_PARSE_OK || $this->getFileSize("replay.initData") == 0) return false; $string = $this->readFile("replay.initData"); $numByte = 0; $numPlayers = MPQFile::readByte($string,$numByte); $playerAdded = false; $playerId = 0; for ($i = 1;$i <= $numPlayers;$i++) { $nickLen = MPQFile::readByte($string,$numByte); if ($nickLen > 0) { $numByte += $nickLen; $numByte += 5; } elseif (!$playerAdded) { // first empty slot $playerAdded = true; $numByte--; if ($i == $numPlayers) $len = 5; else $len = 6; // add the player to the initdata file $obsNameLength = strlen($obsName); $repString = chr($obsNameLength) . $obsName . str_repeat(chr(0),5); $newData = substr($string,0,$numByte) . $repString . substr($string,$numByte - $len + strlen($repString)); $numByte += strlen($repString); //$this->replaceFile("replay.initData", $newData); $playerId = $i; $string = $newData; // skip the next null part because it is 1 byte shorter than normal if ($i < $numPlayers) { $i++; $numByte += 4; } } else { $numByte += 5; } } if ($this->debug) $this->debug(sprintf("Got past first player loop, counter = $i, numbyte: %04X",$numByte)); if ($playerId == 0) return false; $numByte += 25; $accountIdentifierLength = MPQFile::readByte($string,$numByte); if ($accountIdentifierLength > 0) $accountIdentifier = MPQFile::readBytes($string,$numByte,$accountIdentifierLength); $numByte += 684; // length seems to be fixed, data seems to vary at least based on number of players while (true) { $str = MPQFile::readBytes($string,$numByte,4); if ($str != 's2ma') { $numByte -= 4; break; } $numByte += 2; // 0x00 0x00 $realm = MPQFile::readBytes($string,$numByte,2); $this->realm = $realm; $numByte += 32; } // start of variable length data portion $numByte += 2; $numPlayers = MPQFile::readByte($string,$numByte); // need to increment numplayers by 1 $string = substr_replace($string, pack("c",$numPlayers + 1), $numByte - 1, 1); $numByte += 4; for ($i = 1;$i <= $numPlayers;$i++) { $firstByte = MPQFile::readByte($string,$numByte); $secondByte = MPQFile::readByte($string,$numByte); if ($this->debug) $this->debug(sprintf("Function addFakeObserver: numplayer: %d, first byte: %02X, second byte: %02X",$i,$firstByte,$secondByte)); switch ($firstByte) { case 0xca: switch ($secondByte) { case 0x20: // player $numByte += 20; break; case 0x28: // player $numByte += 24; break; case 0x04: case 0x02: // spectator case 0x00: // computer $numByte += 4; break; } break; case 0xc2: switch ($secondByte) { case 0x04: $tmp = MPQFile::readByte($string,$numByte); if ($tmp == 0x05) $numByte += 4; $numByte += 20; break; case 0x24: case 0x44: $numByte += 5; break; } break; default: if ($this->debug) $this->debug(sprintf("Function addFakeObserver: Unknown byte at byte offset %08X, got %02X",$numByte,$firstByte)); return false; } } // insert join game event and initial camera event for the newly created player $string = $this->readFile("replay.game.events"); $string = substr_replace($string, pack("c3",0,$playerId,0x0B),0,0); $tmpByte = $i * 3 + 5; $camerastring = pack("c2", 0, (0x60 | $playerId)) .MPQFile::readBytes($string,$tmpByte,11); $string = substr_replace($string, $camerastring,$tmpByte,0); $this->replaceFile("replay.game.events", $string); return $playerId; } private function parseKeyVal($string, &$numByte) { $one = unpack("C",substr($string,$numByte,1)); $one = $one[1]; $retVal = $one & 0x7F; $shift = 1; $numByte++; while (($one & 0x80) > 0) { $one = unpack("C",substr($string,$numByte,1)); $one = $one[1]; $retVal = (($one & 0x7F) << $shift*7) | $retVal; $shift++; $numByte++; } return $retVal; } function deflate_decompress($string) { if (function_exists("gzinflate")){ $tmp = gzinflate(substr($string,2,strlen($string) - 2)); return $tmp; } if ($this->debug) $this->debug("Function 'gzinflate' does not exist, is gzlib installed as a module?"); return false; } function bzip2_decompress($string) { if (function_exists("bzdecompress")){ $tmp = bzdecompress($string); if (is_numeric($tmp) && $this->debug) { $this->debug(sprintf("Bzip2 returned error code: %d",$tmp)); } return $tmp; } if ($this->debug) $this->debug("Function 'bzdecompress' does not exist, is bzip2 installed as a module?"); return false; } static function initCryptTable() { if (!self::$cryptTable) self::$cryptTable = array(); $seed = 0x00100001; $index1 = 0; $index2 = 0; for ($index1 = 0; $index1 < 0x100; $index1++) { for ($index2 = $index1, $i = 0; $i < 5; $i++, $index2 += 0x100) { $seed = (uPlus($seed * 125,3)) % 0x2AAAAB; $temp1 = ($seed & 0xFFFF) << 0x10; $seed = (uPlus($seed * 125,3)) % 0x2AAAAB; $temp2 = ($seed & 0xFFFF); self::$cryptTable[$index2] = ($temp1 | $temp2); } } } static function hashStuff($string, $hashType) { $seed1 = 0x7FED7FED; $seed2 = ((0xEEEE << 16) | 0xEEEE); $strLen = strlen($string); for ($i = 0;$i < $strLen;$i++) { $next = ord(strtoupper(substr($string, $i, 1))); $seed1 = self::$cryptTable[($hashType << 8) + $next] ^ (uPlus($seed1,$seed2)); $seed2 = uPlus(uPlus(uPlus(uPlus($next,$seed1),$seed2),$seed2 << 5),3); } return $seed1; } static function decryptStuff($data, $key) { $seed = ((0xEEEE << 16) | 0xEEEE); $datalen = count($data); for($i = 0;$i < $datalen;$i++) { $seed = uPlus($seed,self::$cryptTable[0x400 + ($key & 0xFF)]); $ch = $data[$i] ^ (uPlus($key,$seed)); $key = (uPlus(((~$key) << 0x15), 0x11111111)) | (rShift($key,0x0B)); $seed = uPlus(uPlus(uPlus($ch,$seed),($seed << 5)),3); $data[$i] = $ch & ((0xFFFF << 16) | 0xFFFF); } return $data; } static function encryptStuff($data, $key) { $seed = ((0xEEEE << 16) | 0xEEEE); $datalen = count($data); for($i = 0;$i < $datalen;$i++) { $seed = uPlus($seed,self::$cryptTable[0x400 + ($key & 0xFF)]); $ch = $data[$i] ^ (uPlus($key,$seed)); $key = (uPlus(((~$key) << 0x15), 0x11111111)) | (rShift($key,0x0B)); $seed = uPlus(uPlus(uPlus($data[$i],$seed),($seed << 5)),3); $data[$i] = $ch & ((0xFFFF << 16) | 0xFFFF); } return $data; }}function microtime_float() { list($usec, $sec) = explode(" ", microtime()); return ((float)$usec + (float)$sec);}// function that adds up two integers without allowing them to overflow to floatsfunction 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));}// right shift without preserving the sign(leftmost) bitfunction rShift($num,$bits) { return (($num >> 1) & 0x7FFFFFFF) >> ($bits - 1);}?>
  9. SC2REPLAY.PHP (OLD)<?php/* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.*/define("MPQ_ERR_LOWREPLAYVERSION", -1);class SC2Replay { public static $gameSpeeds = array(0 => "Slower", 1=> "Slow", 2=> "Normal", 3=> "Fast", 4=> "Faster"); public static $gameSpeedCE = array(0 => 39, 1=> 44, 2=> 60, 3=> 64, 4=> 64); // estimates, weird values public static $colorIndices = array(1 => "Red", 2=> "Blue", 3=> "Teal", 4=> "Purple", 5=> "Yellow", 6 => "Orange", 7=> "Green", 8=> "Pink"); private $players; //array, indices: color, team, sname, lname, race, startRace, handicap, ptype private $gameLength; // game length in seconds private $mapName; private $gameSpeed; // game speed, number from 0-4. see $gameSpeeds array above private $teamSize; // team size in the format xvx, eg. 1v1 private $gamePublic; private $version; private $build; private $events; // contains an array of the events in replay.game.events file private $debug; // debug, currently true or false private $debugNewline; // contents are appended to the end of all debug messages private $messages; // contains an array of the chat log messages function __construct() { $this->players = array(); $this->gameLength = 0; $this->mapName = NULL; $this->gameSpeed = 0; $this->teamSize = NULL; $this->debug = false; $this->debugNewline = "<br />\n"; } // parameter needs to be an instance of MPQFile function parseReplay($mpqfile) { if (!is_a($mpqfile, "MPQFile")) return false; $this->gameLength = $mpqfile->getGameLength(); if ($mpqfile->getBuild() < 15097) { if ($this->debug) $this->debug("Too low replay version"); return MPQ_ERR_LOWREPLAYVERSION; //demo format changed at patch 9, no support for older ones } $this->version = $mpqfile->getVersion(); $this->build = $mpqfile->getBuild(); // first parse replay.details file $file = $mpqfile->readFile("replay.details"); if ($file !== false) { $this->parseDetailsFile($file); } else if ($this->debug) $this->debug("Error reading the replay.details file"); $file = $mpqfile->readFile("replay.attributes.events"); if ($file !== false) { $this->parseAttributesFile($file);// the following is fallback game length calculation, left here in case the value in mpq header ever breaks or goes away// $fs = $mpqfile->getFileSize("replay.sync.events");// if ($fs !== false) $this->gameLength = $fs / self::$gameSpeedCE[$this->gameSpeed]; // sync event is 4 bytes, with a sync window of 1/8th to 1/16th of a second <---- UNCERTAIN, a theory } else if ($this->debug) $this->debug("Error reading the replay.attributes.events file"); $file = $mpqfile->readFile("replay.game.events"); if (file !== false) $this->parseGameEventsFile($file); else if ($this->debug) $this->debug("Error reading the replay.game.events file"); $file = $mpqfile->readFile("replay.message.events"); if (file !== false) $this->parseChatLog($file); else if ($this->debug) $this->debug("Error reading the replay.message.events file"); } private function debug($message) { echo $message.($this->debugNewline); } function setDebugNewline($str) { $this->debugNewline = $str; } function setDebug($num) { $this->debug = $num; } function getPlayers() { return $this->players; } function getMapName() { return $this->mapName; } function getGameSpeed() { return $this->gameSpeed; } function getGameSpeedText() { return self::$gameSpeeds[$this->gameSpeed]; } function getTeamSize() { return $this->teamSize; } function getVersion() { return $this->version; } function getBuild() { return $this->build; } function getMessages() { return $this->messages; } // getFormattedGameLength returns the time in h hrs, m mins, s secs function getFormattedGameLength() { $hrs = floor($this->gameLength / 3600); $mins = floor($this->gameLength / 60); $secs = $this->gameLength % 60; if ($hrs > 0) $o = "$hrs hrs, "; if ($mins > 0) $o .= "$mins mins, "; $o .= "$secs secs"; return $o; } function getEvents() { return $this->events; } function getGameLength() { return $this->gameLength; } // parse replay.details file and add parsed stuff to the object // $string contains the contents of the file function parseDetailsFile($string) { if ($this->debug) $this->debug("Parsing replay.details file..."); $numByte = 0; $numByte += 6; $numPlayers = $this->readByte($string,$numByte) / 2; for ($i = 0; $i < $numPlayers;$i++) { $p = $this->parsePlayerStruct($string,$numByte); if ($p !== NULL) { $p['id'] = $i; $this->players[$i] = $p; } } $mapnameLen = $this->readByte($string,$numByte) / 2; $this->mapName = $this->readBytes($string,$numByte,$mapnameLen); $numByte += 2; // 04 02 $u1Len = $this->readByte($string,$numByte) / 2; if ($u1Len > 0) $this->readByte($string,$numByte,$u1Len); $numByte += 5; // 06 05 02 00 02 $minimapnameLen = $this->readByte($string,$numByte) / 2; $minimapName = $this->readBytes($string,$numByte,$minimapnameLen); } // parse a player struct in the replay.details file private function parsePlayerStruct($string,&$numByte) { $numByte += 4; $sNameLen = $this->readByte($string,$numByte) / 2; if ($sNameLen > 0) $sName = $this->readBytes($string,$numByte,$sNameLen); else $sName = NULL; $numByte += 5; // 02 05 08 00 09 $numByte += 4; // 00/04 02 07 00 $numByte += 3; // 00 00 00 // 00 53 32 (S2) $hadKey = true; $keys = array(); while ($hadKey) { $hadKey = false; $key = unpack("c2",$this->readBytes($string,$numByte,2)); if ($key[2] == 9) { $hadKey = true; $keys[$key[1]] = $this->parseKeyVal($string,$numByte); } else if ($key[1] == 6 && $key[2] == 2) { break; } } if ($this->debug) { foreach ($keys as $k => $v) $this->debug("Got pre-longname($sName) key: $k, value: $v"); } $lNameLen = $this->readByte($string,$numByte) / 2; if ($lNameLen > 0) $lName = $this->readBytes($string,$numByte,$lNameLen); else $lName = NULL; $numByte += 2; // 04 02 $raceLen = $this->readByte($string,$numByte) / 2; if ($raceLen > 0) $race = $this->readBytes($string,$numByte,$raceLen); else $race = NULL; $numByte += 3; // 06 05 08 $hadKey = true; while ($hadKey) { $keyVal = ""; $hadKey = false; $key = unpack("c2",$this->readBytes($string,$numByte,2)); if ($key[2] == 9) { $hadKey = true; $keyVal = $this->parseKeyVal($string,$numByte); if ($key[1] == 2) { $cR = $keyVal; } // red color if ($key[1] == 4) { $cG = $keyVal; } // green color if ($key[1] == 6) { $cB = $keyVal; } // blue color if ($key[1] == 16) { $party = $keyVal / 2; } // party number? if ($this->debug) $this->debug(sprintf("%s Key: %d, value: %d",$sName,$key[1], $keyVal)); } else if ($key[1] == 5 && $key[2] == 18) {$numByte -= 2; break; } // next player else if ($key[1] == 2 && $key[2] == 2) { break; } // end of player section } if (($sName === NULL) && ($lName === NULL)) { if ($this->debug) $this->debug("Got null player"); return NULL; } $p = array(); $p["sName"] = $sName; $p["lName"] = $lName; $p["race"] = $race; $p["party"] = $party; $p["color"] = sprintf("%02X%02X%02X",$cR,$cG,$cB); if ($this->debug) $this->debug(sprintf("Got player: %s (%s), Race: %s, Party: %s, Color: %s",$sName, $lName, $race, $party, $p["color"])); return $p; } // parameter is the contents of the replay.attributes.events file private function parseAttributesFile($string) { if ($this->debug) $this->debug("Parsing replay.attributes.events file"); $numByte = 4; // skip the 4-byte header $numAttribs = $this->readUInt32($string,$numByte); for ($i = 0;$i < $numAttribs;$i++) { $attribHeader = $this->readUInt16($string,$numByte); $numByte += 2; //skip the 00 00 bytes $attributeId = $this->readUInt16($string,$numByte); $numByte += 2; //skip another 00 00 bytes $playerId = $this->readByte($string,$numByte); $attribVal = ""; // values are stored in reverse in the file, eg Terr becomes rreT. The following loop flips the value and removes excess null bytes for ($a = 0;$a < 4;$a++) { $b = ord(substr($string,$numByte + 3 - $a)); if ($b != 0) $attribVal .= chr($; } $numByte += 4; if ($this->debug) $this->debug(sprintf("Got attrib \"%04X\" for player %d (%s), attribVal = \"%s\"", $attributeId,$playerId,(($playerId == 0x10)?"ALL":$this->players[$playerId]["sName"]),$attribVal)); switch ($attributeId) { case 0x07D3: // team, VERY uncertain because of gazillion other 0x07DX values $this->players[$playerId]["team"] = intval(substr($attribVal,1)); break; case 0x0BBB: // handicap $this->players[$playerId]["handicap"] = $attribVal; break; case 0x0BB8: // game speed if ($this->build >= 15449) { switch ($attribVal) { case "Fasr": $tmp = 4; break; case "Fast": $tmp = 3; break; default: $tmp = 2; } $this->gameSpeed = $tmp; } else { $this->gameSpeed = intval($attribVal); } break; case 0x01F4: // player type, Humn or Comp $this->players[$playerId]["ptype"] = $attribVal; break; case 0x0BB9: // initial race, Prot Terr Zerg or RAND $this->players[$playerId]["srace"] = $attribVal; break; case 0x07D1: // teamsizes $this->teamSize = $attribVal; break; case 0x0BC1: // game type, private(Priv)/open(Amm)? $this->gamePublic = (($attribVal == "Priv")?false:true); break; case 0x0BBA: // color index if ($this->build >= 15449) { $this->players[$playerId]["colorIndex"] = intval(substr($attribVal,2)); $this->players[$playerId]["sColor"] = self::$colorIndices[intval(substr($attribVal,2))]; } else { $this->players[$playerId]["colorIndex"] = intval($attribVal); $this->players[$playerId]["sColor"] = self::$colorIndices[intval($attribVal)]; } break; default: } } } // parse a key/value -pair struct in the replay.details file private function parseKeyVal($string, &$numByte) { $one = $this->readByte($string,$numByte); //$one[1]; if (($one & 192) > 0) { // check if value is two bytes $two = unpack("v",substr($string,$numByte -1,2)); $two = ($two[1] >> 2); // get rid of extra bits $numByte += 1; return $two; } return $one; } private function readByte($string, &$numByte) { $tmp = unpack("C",substr($string,$numByte,1)); $numByte++; return $tmp[1]; } private function readBytes($string, &$numByte, $length) { $tmp = substr($string,$numByte,$length); $numByte += $length; return $tmp; } private function readUInt16($string, &$numByte) { $tmp = unpack("v",substr($string,$numByte,2)); $numByte += 2; return $tmp[1]; } private function readUInt32($string, &$numByte) { $tmp = unpack("V",substr($string,$numByte,4)); $numByte += 4; return $tmp[1]; } private function readUnitTypeID($string,&$numByte) { return (($this->readByte($string,$numByte) << 16) | ($this->readByte($string,$numByte) << 8) | ($this->readByte($string,$numByte))); } private function readUnitAbility($string) { $bytes = unpack("C3",substr($string,4,3)); return (($bytes[1] << 16) | ($bytes[2] << 8) | ($bytes[3])); } // gets players who actually played in the game, meaning excludes observers and party members. public function getActualPlayers() { $tmp = array(); foreach ($this->players as $val) if ($val['party'] > 0) $tmp[] = $val; return $tmp; } // parameter is the contents of the replay.message.events -file private function parseChatLog($string) { $numByte = 0; $len = strlen($string); $messages = array(); $totTime = 0; while ($numByte < $len) { $timestamp = $this->parseTimeStamp($string,$numByte); $playerId = $this->readByte($string,$numByte); $opcode = $this->readByte($string,$numByte); $totTime += $timestamp; if ($opcode == 0x80) // header weird thingy? $numByte += 4; else if ($opcode == 0x00 || $opcode == 0x02 || $opcode == 0x0a) { // message $messageTarget = $opcode & 3; $messageLength = $this->readByte($string,$numByte); if ($opcode == 0x0a) $messageLength += 64; $message = $this->readBytes($string,$numByte,$messageLength); $messages[] = array('id' => $playerId, 'name' => $this->players[$playerId]['sName'], 'target' => $messageTarget, 'time' => floor($totTime / 16), 'message' => $message); } else if ($opcode == 0x83) { // ping on map? 9 bytes? $numByte += 9; } } $this->messages = $messages; } // parameter is the contents of the replay.game.events -file private function parseGameEventsFile($string) { $numByte = 0; $len = strlen($string); $playerLeft = array(); $events = array(); $time = 0; while ($numByte < $len) { $timeStamp = $this->parseTimeStamp($string,$numByte); $nextByte = $this->readByte($string,$numByte); $eventType = $nextByte >> 5; // 3 lowest bits $globalEventFlag = $nextByte & 16; // 4th bit $playerId = $nextByte & 15; // bits 5-8 $playerName = $this->players[$playerId]['sname']; $eventCode = $this->readByte($string,$numByte); $time += $timeStamp; // weird timestamp values mean that there's likely a problem with the alignment of the parse(too few/too many bytes read for an eventcode) if ($this->debug >= 2) { if ($len - $numByte > 24) { $bytes = unpack("C24",substr($string,$numByte,24)); $dataBytes = ""; for ($i = 1;$i <= 24;$i++) $dataBytes .= sprintf("%02X",$bytes[$i]); $this->debug(sprintf("DEBUG: Timestamp: %d, Type: %d, Global: %d, Player ID: %d (%s), Event code: %02X Byte: %08X, Data: %s<br />\n", $timeStamp, $eventType, $globalEventFlag,$playerId,$playerName,$eventCode,$numByte,$dataBytes)); } } switch ($eventType) { case 0x00: // initialization switch ($eventCode) { case 0x1B: // Player enters game case 0x0B: break; case 0x05: // game starts break; default: if ($this->debug) $this->debug(sprintf("DEBUG: Timestamp: %d, Type: %d, Global: %d, Player ID: %d (%s), Event code: %02X Byte: %08X<br />\n", $timeStamp, $eventType, $globalEventFlag,$playerId,$playerName,$eventCode,$numByte)); } break; case 0x01: // action switch ($eventCode) { case 0x09: // player quits the game if ($this->players[$playerId]['party'] > 0) // don't log observers/party members etc $playerLeft[] = $playerId; break; case 0x0B: // player uses an ability // at least 32 bytes $data = $this->readBytes($string,$numByte,32); $reqTarget = unpack("C",substr($data,7,1)); $reqTarget = $reqTarget[1]; $ability = $this->readUnitAbility($data); if ($ability != 0xFFFF0F) { $events[] = array('p' => $playerId, 't' => $time, 'a' => $ability); $this->events = $events; } // at least with attack, move, right-click, if the byte after unit ability bytes is // 0x30 or 0x50, the struct takes 1 extra byte. With build orders the struct seems to be 32 bytes // and this byte is 0x00. // might also be in some other way variable-length. if ($reqTarget == 0x30) $data .= $this->readByte($string,$numByte); if ($reqTarget == 0x50) $data .= $this->readByte($string,$numByte); // update apm array $this->players[$playerId]['apmtotal']++; $this->players[$playerId]['apm'][floor($time / 16)]++; break; case 0x2F: // player sends resources $numByte += 17; // data is 17 bytes long break; case 0x0C: // automatic update of hotkey? case 0x1C: case 0x2C: case 0x3C: // 01 01 01 01 11 01 03 02 02 38 00 01 02 3c 00 01 00 case 0x4C: // 01 02 02 01 0d 00 02 01 01 a8 00 00 01 case 0x5C: // 01 01 01 01 16 03 01 01 03 18 00 01 00 case 0x6C: // 01 04 08 01 03 00 02 01 01 34 c0 00 01 case 0x7C: // 01 05 10 01 01 10 02 01 01 1a a0 00 01 case 0x8C: case 0x9C: case 0xAC: // player changes selection $selFlags = $this->readByte($string,$numByte); $dsuCount = $this->readByte($string,$numByte); $dsuExtraBits = $dsuCount % 8; if ($dsuCount > 0) $dsuMap = $this->readBytes($string,$numByte,floor($dsuCount / 8)); if ($dsuExtraBits != 0) { // not byte-aligned $dsuMapLastByte = $this->readByte($string,$numByte); $nByte = $this->readByte($string,$numByte); $uTypesCount = (($dsuMapLastByte & (0xFF - ((1 << $dsuExtraBits) - 1))) | ($nByte & (0xFF >> (8 - $dsuExtraBits)))); for ($i = 1;$i <= $uTypesCount;$i++) { $n3Bytes = unpack("C3",$this->readBytes($string,$numByte,3)); $tmp = (($nByte & (0xFF - ((1 << $dsuExtraBits) - 1))) | ($nBytes[1] & (0xFF >> (8 - $dsuExtraBits)))); $tmp2 = (($nBytes[1] & (0xFF - ((1 << $dsuExtraBits) - 1))) | ($nBytes[2] & (0xFF >> (8 - $dsuExtraBits)))); $tmp3 = (($nBytes[2] & (0xFF - ((1 << $dsuExtraBits) - 1))) | ($nBytes[3] & (0xFF >> (8 - $dsuExtraBits)))); $tmp = ($tmp << 16) | ($tmp2 << 8) | $tmp3; $uType[$i]['id'] = $tmp; $nByte = $this->readByte($string,$numByte); $tmp = (($nBytes[2] & (0xFF - ((1 << $dsuExtraBits) - 1))) | ($nByte & (0xFF >> (8 - $dsuExtraBits)))); $uType[$i]['count'] = $tmp; } $lByte = $this->readByte($string,$numByte); $tmp = (($nByte & (0xFF - ((1 << $dsuExtraBits) - 1))) | ($lByte & (0xFF >> (8 - $dsuExtraBits)))); $totalUnits = $tmp; //unnecessary to parse unit ID values at this point, so skip them $numByte += $totalUnits * 4; } else { // byte-aligned $uTypesCount = $this->readByte($string,$numByte); for ($i = 1;$i <= $uTypesCount;$i++) { $uType[$i]['id'] = $this->readUnitTypeID($string,$numByte); $uType[$i]['count'] = $this->readByte($string,$numByte); } $totalUnits = $this->readByte($string,$numByte); //unnecessary to parse unit ID values at this point, so skip them $numByte += $totalUnits * 4; } //update apm fields if ($eventCode == 0xAC) { $this->players[$playerId]['apmtotal']++; $this->players[$playerId]['apm'][floor($time / 16)]++; } break; case 0x0D: // manually uses hotkey case 0x1D: case 0x2D: case 0x3D: case 0x4D: case 0x5D: case 0x6D: case 0x7D: case 0x8D: case 0x9D: $byte1 = $this->readByte($string,$numByte); $byte2 = $this->readByte($string,$numByte); $extraBytes = floor($byte1 / 8); $numByte += $extraBytes; $extraExtraByte = ((($byte1 & 4) == 4) && (($byte2 & 3) == 3))?1:0; $numByte += $extraExtraByte; // update apm $this->players[$playerId]['apmtotal']++; $this->players[$playerId]['apm'][floor($time / 16)]++; break; case 0x1F: // no idea $numByte += 17; // 84 00 00 0c 84 00 00 00 80 00 00 00 80 00 00 00 00 break; default: if ($this->debug) $this->debug(sprintf("DEBUG: Timestamp: %d, Type: %d, Global: %d, Player ID: %d (%s), Event code: %02X Byte: %08X<br />\n", $timeStamp, $eventType, $globalEventFlag,$playerId,$playerName,$eventCode,$numByte)); } break; case 0x02: // weird switch($eventCode) { case 0x06: $numByte += 8; // 00 00 00 04 00 00 00 04 break; default: if ($this->debug) $this->debug(sprintf("DEBUG: Timestamp: %d, Type: %d, Global: %d, Player ID: %d (%s), Event code: %02X Byte: %08X<br />\n", $timeStamp, $eventType, $globalEventFlag,$playerId,$playerName,$eventCode,$numByte)); } break; case 0x03: // replay switch ($eventCode) { case 0x81: // player moves screen $numByte += 20; // always 20 bytes break; default: if ($this->debug) $this->debug(sprintf("DEBUG: Timestamp: %d, Type: %d, Global: %d, Player ID: %d (%s), Event code: %02X Byte: %08X<br />\n", $timeStamp, $eventType, $globalEventFlag,$playerId,$playerName,$eventCode,$numByte)); } break; case 0x04: // inaction switch($eventCode) { case 0x00: //automatic synchronization $numByte += 4; break; case 0x16: $numByte += 24; break; case 0x18: $numByte += 4; break; case 0x1C: case 0x2C: // no data break; default: if ($this->debug) $this->debug(sprintf("DEBUG: Timestamp: %d, Type: %d, Global: %d, Player ID: %d (%s), Event code: %02X Byte: %08X<br />\n", $timeStamp, $eventType, $globalEventFlag,$playerId,$playerName,$eventCode,$numByte)); } break; case 0x05: // system switch($eventCode) { case 0x89: //automatic synchronization? $numByte += 4; break; default: if ($this->debug) $this->debug(sprintf("DEBUG: Timestamp: %d, Type: %d, Global: %d, Player ID: %d (%s), Event code: %02X Byte: %08X<br />\n", $timeStamp, $eventType, $globalEventFlag,$playerId,$playerName,$eventCode,$numByte)); } break; default: if ($this->debug) $this->debug(sprintf("DEBUG: Timestamp: %d, Type: %d, Global: %d, Player ID: %d (%s), Event code: %02X Byte: %08X<br />\n", $timeStamp, $eventType, $globalEventFlag,$playerId,$playerName,$eventCode,$numByte)); } } // update winners based on $playerLeft -array $numLeft = count($playerLeft); $numActual = count($this->getActualPlayers()); $lastLeaver = -1; foreach ($playerLeft as $val) { // mark the previous leaver as a loser if ($lastLeaver != -1) $this->players[$val]['won'] = 0; $lastLeaver = $val; } // if the number of players who left is $numActual - 1, then everyone else except the recorder left and he is the winner // if the number of players who left is $numActual - 2, then whoever left after the recorder is the winner. can be determined if the recorder is known. // otherwise the winner cannot be determined, since any one of the players who left after the recorder could be the winner if ($numLeft == ($numActual - 1)) { if ($this->debug) $this->debug("Found winner"); $this->players[$lastLeaver]['won'] = 0; } else { if ($this->debug) $this->debug("Unable to parse winner"); return; } foreach ($this->players as $val) { if (($val['party'] > 0) && (!isset($val['won']))) $winteam = $val['party']; } foreach ($this->players as $val) { if ($val['party'] == $winteam) $this->players[$val['id']]['won'] = 1; else if ($val['party'] > 0) $this->players[$val['id']]['won'] = 0; } } private function parseTimeStamp($string, &$numByte) { $one = $this->readByte($string,$numByte); if (($one & 3) > 0) { // check if value is two bytes or more $two = $this->readByte($string,$numByte); $two = ((($one >> 2 ) << 8) | $two); if (($one & 3) >= 2) { $tmp = $this->readByte($string,$numByte); $two = (($two << 8) | $tmp); if (($one & 3) == 3) { $tmp = $this->readByte($string,$numByte); $two = (($two << 8) | $tmp); } } return $two; } return ($one >> 2); } // gets the literal string from the sc2_abilitycodes array based on the ability code // returns false if the variable doesn't exist or the file cannot be included function getAbilityString($num) { global $sc2_abilityCodes; if (isset($sc2_abilityCodes) || (include 'abilitycodes.php')) { if ($this->build >= 15449) { $num -= 0x400; if ((($num & 0x06FD00) == 0x06FD00) || (($num & 0x08F000) == 0x08F000)) $num -= 0x00F000; } if ($this->debug) return sprintf("%s (%06X)",$sc2_abilityCodes[$num],$num); else return $sc2_abilityCodes[$num]; } return false; }}?>
  10. MPQFILE.PHP (OLD)<?php/* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.*/define("MPQ_HASH_TABLE_OFFSET", 0);define("MPQ_HASH_NAME_A", 1);define("MPQ_HASH_NAME_B", 2);define("MPQ_HASH_FILE_KEY", 3);define("MPQ_HASH_ENTRY_EMPTY", (0xFFFF << 16) | 0xFFFF);define("MPQ_HASH_ENTRY_DELETED", (0xFFFF << 16) | 0xFFFE);define("MPQ_NOT_PARSED", 2);define("MPQ_PARSE_OK", 1);define("MPQ_ERR_NOTMPQFILE", -1);class MPQFile { private $filename; private $fp; private $hashtable,$blocktable; private $hashTableSize, $blocKTableSize; private $headerOffset; private $init; private $verMajor; private $build; private $sectorSize; private $debug; private $debugNewline; private $gameLen; public static $cryptTable; function __construct($filename, $autoparse = true, $debug = 0) { $this->filename = $filename; $this->hashtable = NULL; $this->blocktable = NULL; $this->hashTableSize = 0; $this->blockTableSize = 0; $this->headerOffset = 0; $this->init = false; $this->verMajor = 0; $this->build = 0; $this->gameLen = 0; $this->sectorSize = 0; $this->debug = $debug; $this->debugNewline = "<br />\n"; if (!self::$cryptTable) self::initCryptTable(); if (file_exists($this->filename)) { $this->fp = fopen($this->filename, 'rb'); if ($this->debug && $this->fp === false) $this->debug("Error opening file $filename for reading"); } if ($autoparse) $this->parseHeader(); } function __destruct() { if ($this->fp !== FALSE) fclose($this->fp); } private function debug($message) { echo $message.($this->debugNewline); } function setDebugNewline($str) { $this->debugNewline = $str; } function setDebug($bool) { $this->debug = $bool; } function parseHeader() { if ($this->fp === FALSE) { return false; if ($this->debug) $this->debug("Invalid file pointer"); } $fp = $this->fp; $headerParsed = false; $headerOffset = 0; while (!$headerParsed) { $magic = unpack("c4",fread($fp,4)); // MPQ 1Bh or 1Ah if (($magic[1] != 0x4D) || ($magic[2] != 0x50) || ($magic[3] != 0x51)) { $this->init = MPQ_ERR_NOTMPQFILE; return false; } if ($magic[4] == 27) { // user data block (1Bh) if ($this->debug) $this->debug(sprintf("Found user data block at %08X",ftell($fp))); $uDataMaxSize = $this->readUInt32(); $headerOffset = $this->readUInt32(); $this->headerOffset = $headerOffset; $uDataSize = $this->readUInt32(); fseek($fp,24,SEEK_CUR); // skip Starcraft II replay 0x1B 0x32 0x01 0x00 $verMajor = $this->readUInt16(); $this->verMajor = $verMajor; $build = $this->readUInt32(true); $this->build = $build; $build2 = $this->readUInt32(true); fseek($fp,2,SEEK_CUR); // skip 02 00 $gameLen = $this->readUInt16(true) / 2; $this->gameLen = $gameLen; fseek($fp,$headerOffset); } else if ($magic[4] == 26) { // header (1Ah) if ($this->debug) $this->debug(sprintf("Found header at %08X",ftell($fp))); $headerSize = $this->readUInt32(); $archiveSize = $this->readUInt32(); $formatVersion = $this->readUInt16(); $sectorSizeShift = $this->readByte(); $sectorSize = 512 * pow(2,$sectorSizeShift); $this->sectorSize = $sectorSize; fseek($fp, 1, SEEK_CUR); $hashTableOffset = $this->readUInt32() + $headerOffset; $blockTableOffset = $this->readUInt32() + $headerOffset; $hashTableEntries = $this->readUInt32(); $this->hashTableSize = $hashTableEntries; $blockTableEntries = $this->readUInt32(); $this->blockTableSize = $blockTableEntries; $headerParsed = true; } else { if ($this->debug) $this->debug("Could not find MPQ header"); return false; } } // read and decode the hash table fseek($this->fp, $hashTableOffset); $hashSize = $hashTableEntries * 4; // hash table size in 4-byte chunks $tmp = array(); for ($i = 0;$i < $hashSize;$i++) $tmp[$i] = $this->readUInt32(); $hashTable = self::decryptStuff($tmp,self::hashStuff("(hash table)", 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 fseek($this->fp, $blockTableOffset); $blockSize = $blockTableEntries * 4; // block table size in 4-byte chunks $tmp = array(); for ($i = 0;$i < $blockSize;$i++) $tmp[$i] = $this->readUInt32(); $blockTable = self::decryptStuff($tmp,self::hashStuff("(block table)", 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 = MPQFILE_PARSE_OK; return true; } // read little endian 32-bit integer private function readUInt32($bigendian = false) { if ($this->fp === FALSE) return false; $t = unpack(($bigendian === true)?"N":"V",fread($this->fp,4)); return $t[1]; } private function readUInt16($bigendian = false) { if ($this->fp === FALSE) return false; $t = unpack(($bigendian === true)?"n":"v",fread($this->fp,2)); return $t[1]; } private function readByte() { if ($this->fp === FALSE) return false; $t = unpack("C",fread($this->fp,1)); return $t[1]; } // read a byte from string and remove the read byte private function readSByte(&$string) { $t = unpack("C",substr($string,0,1)); $string = substr($string,1,strlen($string) -1); return $t[1]; } function getFileSize($filename) { if ($this->init !== MPQFILE_PARSE_OK) { if ($this->debug) $this->debug("Tried to use getFileSize without initializing"); return false; } $hashA = self::hashStuff($filename, MPQ_HASH_NAME_A); $hashB = self::hashStuff($filename, MPQ_HASH_NAME_; $hashStart = self::hashStuff($filename, MPQ_HASH_TABLE_OFFSET) & ($this->hashTableSize - 1); $tmp = $hashStart; do { if (($this->hashtable[$tmp*4 + 3] == MPQ_HASH_ENTRY_DELETED) || ($this->hashtable[$tmp*4 + 3] == MPQ_HASH_ENTRY_EMPTY)) return false; if (($this->hashtable[$tmp*4] == $hashA) && ($this->hashtable[$tmp*4 + 1] == $hashB)) { // found file $blockIndex = ($this->hashtable[($tmp *4) + 3]) *4; $fileSize = $this->blocktable[$blockIndex + 2]; return $fileSize; } $tmp = ($tmp + 1) % $this->hashTableSize; } while ($tmp != $hashStart); if ($this->debug) $this->debug("Did not find file $filename in archive"); return false; } function readFile($filename) { if ($this->init !== MPQFILE_PARSE_OK) { if ($this->debug) $this->debug("Tried to use getFile without initializing"); return false; } $hashA = self::hashStuff($filename, MPQ_HASH_NAME_A); $hashB = self::hashStuff($filename, MPQ_HASH_NAME_; $hashStart = self::hashStuff($filename, MPQ_HASH_TABLE_OFFSET) & ($this->hashTableSize - 1); $tmp = $hashStart; $blockSize = -1; do { if (($this->hashtable[$tmp*4 + 3] == MPQ_HASH_ENTRY_DELETED) || ($this->hashtable[$tmp*4 + 3] == MPQ_HASH_ENTRY_EMPTY)) return false; if (($this->hashtable[$tmp*4] == $hashA) && ($this->hashtable[$tmp*4 + 1] == $hashB)) { // found file $blockIndex = ($this->hashtable[($tmp *4) + 3]) *4; $blockOffset = $this->blocktable[$blockIndex] + $this->headerOffset; $blockSize = $this->blocktable[$blockIndex + 1]; $fileSize = $this->blocktable[$blockIndex + 2]; $flags = $this->blocktable[$blockIndex + 3]; break; } $tmp = ($tmp + 1) % $this->hashTableSize; } while ($tmp != $hashStart); if ($blockSize == -1) { if ($this->debug) $this->debug("Did not find file $filename in archive"); return false; } $flag_file = $flags & 0x80000000; $flag_checksums = $flags & 0x04000000; $flag_deleted = $flags & 0x02000000; $flag_singleunit = $flags & 0x01000000; $flag_hEncrypted = $flags & 0x00020000; $flag_encrypted = $flags & 0x00010000; $flag_compressed = $flags & 0x00000200; $flag_imploded = $flags & 0x00000100; if ($this->debug) $this->debug(sprintf("Found file $filename with flags %08X, block offset %08X, block size %d and file size %d", $flags, $blockOffset,$blockSize,$fileSize)); if (!$flag_file) return false; fseek($this->fp,$blockOffset); if ($flag_checksums) { for ($i = $fileSize;$i > 0;$i -= $this->sectorSize) { $sectors[] = $this->readUInt32(); $blockSize -= 4; } $sectors[] = $this->readUInt32(); $blockSize -= 4; } else { $sectors[] = 0; $sectors[] = $blockSize; } $c = count($sectors) - 1; $totDur = 0; for ($i = 0;$i < $c;$i++) { $sectorLen = $sectors[$i + 1] - $sectors[$i]; fseek($this->fp,$blockOffset + $sectors[$i],SEEK_SET); $sectorData = fread($this->fp,$sectorLen); if ($flag_compressed && (($flag_singleunit && ($blockSize < $fileSize)) || ($flag_checksums && ($sectorLen < $this->sectorSize)))) { $compressionType = $this->readSByte($sectorData); switch ($compressionType) { case 2: $output .= self::deflate_decompress($sectorData); break; default: if ($this->debug) $this->debug(sprintf("Unknown compression type: %d",$compressionType)); return false; } } else $output .= $sectorData; } if (strlen($output) != $fileSize) { if ($this->debug) $this->debug(sprintf("Decrypted/uncompressed file size(%d) does not match original file size(%d)", strlen($output),$fileSize)); return false; } return $output; } function parseReplay() { if ($this->init !== MPQFILE_PARSE_OK) { if ($this->debug) $this->debug("Tried to use parseReplay without initializing"); return false; } if (class_exists("SC2Replay") || (include 'sc2replay.php')) { $tmp = new SC2Replay(); if ($this->debug) $tmp->setDebug($this->debug); $tmp->parseReplay($this); return $tmp; } else { if ($this->debug) $this->debug("Unable to find or load class SC2Replay"); return false; } } function getState() { return $this->init; } function getBuild() { return $this->build; } function getVersion() { return $this->verMajor; } function getGameLength() { return $this->gameLen; } static function deflate_decompress($string) { if (function_exists("gzinflate")){ $tmp = gzinflate(substr($string,2,strlen($string) - 2)); return $tmp; } if ($this->debug) $this->debug("Function 'gzinflate' does not exist, is gzlib installed as a module?"); return false; } static function initCryptTable() { if (!self::$cryptTable) self::$cryptTable = array(); $seed = 0x00100001; $index1 = 0; $index2 = 0; for ($index1 = 0; $index1 < 0x100; $index1++) { for ($index2 = $index1, $i = 0; $i < 5; $i++, $index2 += 0x100) { $seed = (uPlus($seed * 125,3)) % 0x2AAAAB; $temp1 = ($seed & 0xFFFF) << 0x10; $seed = (uPlus($seed * 125,3)) % 0x2AAAAB; $temp2 = ($seed & 0xFFFF); self::$cryptTable[$index2] = ($temp1 | $temp2); } } } static function hashStuff($string, $hashType) { $seed1 = 0x7FED7FED; $seed2 = ((0xEEEE << 16) | 0xEEEE); $strLen = strlen($string); for ($i = 0;$i < $strLen;$i++) { $next = ord(strtoupper(substr($string, $i, 1))); $seed1 = self::$cryptTable[($hashType << 8) + $next] ^ (uPlus($seed1,$seed2)); $seed2 = uPlus(uPlus(uPlus(uPlus($next,$seed1),$seed2),$seed2 << 5),3); } return $seed1; } static function decryptStuff($data, $key) { $seed = ((0xEEEE << 16) | 0xEEEE); $datalen = count($data); for($i = 0;$i < $datalen;$i++) { $seed = uPlus($seed,self::$cryptTable[0x400 + ($key & 0xFF)]); $ch = $data[$i] ^ (uPlus($key,$seed)); $data[$i] = $ch & ((0xFFFF << 16) | 0xFFFF); $key = (uPlus(((~$key) << 0x15), 0x11111111)) | (rShift($key,0x0B)); $seed = uPlus(uPlus(uPlus($ch,$seed),($seed << 5)),3); } return $data; }}function microtime_float() { list($usec, $sec) = explode(" ", microtime()); return ((float)$usec + (float)$sec);}// function that adds up two integers without allowing them to overflow to floatsfunction 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));}// right shift without preserving the sign(leftmost) bitfunction rShift($num,$bits) { return (($num >> 1) & 0x7FFFFFFF) >> ($bits - 1);}?>
  11. Yep that seemed to fix the problem in which it was linking to the Google Page, but it presented something else now.Now it's just refreshing the Upload Form when I upload a file, rather than displaying the results. I'll post the old MPQFile.php and the old SC2Replay.php Files, as well as the two which replaced them. Here's the coding for the old files:
  12. I see what's going on... I pasted the HTML Coding, not the PHP Coding
  13. --> Removed. I'll find a better way of posting it.
  14. --> Removed. I'll find a better way of posting it.
  15. --> Removed. I'll find a better way of posting it.
×
×
  • Create New...