From 47a8c5bcfeae4bbe80d3583e992a877fbefc0644 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20Schro=CC=88der?= Date: Thu, 26 Jun 2014 20:50:22 +0200 Subject: [PATCH] removed unused lib gbxdatafetcher --- .../GbxDataFetcher/gbxdatafetcher.inc.php | 1431 ----------------- 1 file changed, 1431 deletions(-) delete mode 100644 application/core/Libs/GbxDataFetcher/gbxdatafetcher.inc.php diff --git a/application/core/Libs/GbxDataFetcher/gbxdatafetcher.inc.php b/application/core/Libs/GbxDataFetcher/gbxdatafetcher.inc.php deleted file mode 100644 index 43fcfe72..00000000 --- a/application/core/Libs/GbxDataFetcher/gbxdatafetcher.inc.php +++ /dev/null @@ -1,1431 +0,0 @@ - - * Thanks to Electron for additional input & prototyping - * Based on information at http://en.tm-wiki.org/wiki/GBX, - * http://www.tm-forum.com/viewtopic.php?p=192817#p192817 - * and http://en.tm-wiki.org/wiki/PAK#Header_versions_8.2B - * - * v2.5: Add lookback string Valley; strip optional digits from mood; fix empty - * thumbnail warning - * v2.4: Update GBXChallMapFetcher & GBXPackFetcher version-dependent processing; - * update lookback strings - * v2.3: Add UTF-8 decoding of parsed XML chunk's elements; strip UTF-8 BOM - * from various string fields - * v2.2: Add class GBXPackFetcher; limit loadGBXdata() to first 256KB - * v2.1: Add exception codes; add $thumbLen to GBXChallMapFetcher - * v2.0: Complete rewrite - */ - -/** - * @class GBXBaseFetcher - * @brief The base GBX class with all common functionality - */ -class GBXBaseFetcher -{ - public $parseXml, $xml, $xmlParsed; - - public $authorVer, $authorLogin, $authorNick, $authorZone, $authorEInfo; - - private $_gbxdata, $_gbxlen, $_gbxptr, $_debug, $_error, $_endianess, - $_lookbacks, $_parsestack; - - // supported class ID's - const GBX_CHALLENGE_TMF = 0x03043000; - const GBX_AUTOSAVE_TMF = 0x03093000; - const GBX_CHALLENGE_TM = 0x24003000; - const GBX_AUTOSAVE_TM = 0x2403F000; - const GBX_REPLAY_TM = 0x2407E000; - - const MACHINE_ENDIAN_ORDER = 0; - const LITTLE_ENDIAN_ORDER = 1; - const BIG_ENDIAN_ORDER = 2; - - const LOAD_LIMIT = 256; // KBs to read in loadGBXdata() - - // initialise new instance - public function __construct() - { - $this->parseXml = false; - $this->xml = ''; - $this->xmlParsed = array(); - $this->_debug = false; - $this->_error = ''; - - list($endiantest) = array_values(unpack('L1L', pack('V', 1))); - if ($endiantest != 1) - $this->_endianess = self::BIG_ENDIAN_ORDER; - else - $this->_endianess = self::LITTLE_ENDIAN_ORDER; - - $this->clearGBXdata(); - $this->clearLookbacks(); - $this->_parsestack = array(); - - $this->authorVer = 0; - $this->authorLogin = ''; - $this->authorNick = ''; - $this->authorZone = ''; - $this->authorEInfo = ''; - } - - // enable debug logging - protected function enableDebug() - { - $this->_debug = true; - } - - // disable debug logging - protected function disableDebug() - { - $this->_debug = false; - } - - // print message to stderr if debugging - protected function debugLog($msg) - { - if ($this->_debug) - fwrite(STDERR, $msg."\n"); - } - - // set error message prefix - protected function setError($prefix) - { - $this->_error = (string)$prefix; - } - - // exit with error exception - protected function errorOut($msg, $code = 0) - { - $this->clearGBXdata(); - throw new Exception($this->_error . $msg, $code); - } - - // read in raw GBX data - protected function loadGBXdata($filename) - { - $gbxdata = @file_get_contents($filename, false, null, 0, self::LOAD_LIMIT * 1024); - if ($gbxdata !== false) - $this->storeGBXdata($gbxdata); - else - $this->errorOut('Unable to read GBX data from ' . $filename, 1); - } - - // store raw GBX data - protected function storeGBXdata($gbxdata) - { - $this->_gbxdata = & $gbxdata; - $this->_gbxlen = strlen($gbxdata); - $this->_gbxptr = 0; - } - - // clear GBX data (to avoid print_r problems & reduce memory usage) - protected function clearGBXdata() - { - $this->storeGBXdata(''); - } - - // get GBX pointer - protected function getGBXptr() - { - return $this->_gbxptr; - } - - // set GBX pointer - protected function setGBXptr($ptr) - { - $this->_gbxptr = (int)$ptr; - } - - // move GBX pointer - protected function moveGBXptr($len) - { - $this->_gbxptr += (int)$len; - } - - // read $len bytes from GBX data - protected function readData($len) - { - if ($this->_gbxptr + $len > $this->_gbxlen) - $this->errorOut(sprintf('Insufficient data for %d bytes at pos 0x%04X', - $len, $this->_gbxptr), 2); - $data = ''; - while ($len-- > 0) - $data .= $this->_gbxdata[$this->_gbxptr++]; - return $data; - } - - // read signed byte from GBX data - protected function readInt8() - { - $data = $this->readData(1); - list(, $int8) = unpack('c*', $data); - return $int8; - } - - // read signed short from GBX data - protected function readInt16() - { - $data = $this->readData(2); - if ($this->_endianess == self::BIG_ENDIAN_ORDER) - $data = strrev($data); - list(, $int16) = unpack('s*', $data); - return $int16; - } - - // read signed long from GBX data - protected function readInt32() - { - $data = $this->readData(4); - if ($this->_endianess == self::BIG_ENDIAN_ORDER) - $data = strrev($data); - list(, $int32) = unpack('l*', $data); - return $int32; - } - - // read string from GBX data - protected function readString() - { - $gbxptr = $this->getGBXptr(); - $len = $this->readInt32(); - $len &= 0x7FFFFFFF; - if ($len <= 0 || $len >= 0x12000) { // for large XML & Data blocks - if ($len != 0) - $this->errorOut(sprintf('Invalid string length %d (0x%04X) at pos 0x%04X', - $len, $len, $gbxptr), 3); - } - $data = $this->readData($len); - return $data; - } - - // strip UTF-8 BOM from string - protected function stripBOM($str) - { - return str_replace("\xEF\xBB\xBF", '', $str); - } - - // clear lookback strings - protected function clearLookbacks() - { - $this->_lookbacks = array(); - } - - // read lookback string from GBX data - protected function readLookbackString() - { - if (empty($this->_lookbacks)) { - $version = $this->readInt32(); - if ($version != 3) - $this->errorOut('Unknown lookback strings version: ' . $version, 4); - } - - // check index - $index = $this->readInt32(); - if ($index == -1) { - // unassigned (empty) string - $str = ''; - } elseif (($index & 0xC0000000) == 0) { - // use external reference string - switch ($index) { - case 11: $str = 'Valley'; - break; - case 12: $str = 'Canyon'; - break; - case 17: $str = 'TMCommon'; - break; - case 202: $str = 'Storm'; - break; - case 299: $str = 'SMCommon'; - break; - case 10003: $str = 'Common'; - break; - default: $str = 'UNKNOWN'; - } - } elseif (($index & 0x3FFFFFFF) == 0) { - // read string & add to lookbacks - $str = $this->readString(); - $this->_lookbacks[] = $str; - } else { - // use string from lookbacks - $str = $this->_lookbacks[($index & 0x3FFFFFFF) - 1]; - } - - return $str; - } - - // XML parser functions - private function startTag($parser, $name, $attribs) - { - foreach ($attribs as &$val) - $val = utf8_decode($val); - //echo 'startTag: ' . $name . "\n"; print_r($attribs); - array_push($this->_parsestack, $name); - if ($name == 'DEP') { - $this->xmlParsed['DEPS'][] = $attribs; - } elseif (count($this->_parsestack) <= 2) { - // HEADER, IDENT, DESC, TIMES, CHALLENGE/MAP, DEPS, CHECKPOINTS - $this->xmlParsed[$name] = $attribs; - } - } - - private function charData($parser, $data) - { - //echo 'charData: ' . $data . "\n"; - if (count($this->_parsestack) == 3) - $this->xmlParsed[$this->_parsestack[1]][$this->_parsestack[2]] = $data; - elseif (count($this->_parsestack) > 3) - $this->debugLog('XML chunk nested too deeply: ', print_r($this->_parsestack, true)); - } - - private function endTag($parser, $name) - { - //echo 'endTag: ' . $name . "\n"; - array_pop($this->_parsestack); - } - - protected function parseXMLstring() - { - // define a dedicated parser to handle the attributes - $xml_parser = xml_parser_create(); - xml_set_object($xml_parser, $this); - xml_set_element_handler($xml_parser, 'startTag', 'endTag'); - xml_set_character_data_handler($xml_parser, 'charData'); - - // escape '&' characters unless already a known entity - $xml = preg_replace('/&(?!(?:amp|quot|apos|lt|gt);)/', '&', $this->xml); - - if (!xml_parse($xml_parser, utf8_encode($xml), true)) - $this->errorOut(sprintf('XML chunk parse error: %s at line %d', - xml_error_string(xml_get_error_code($xml_parser)), - xml_get_current_line_number($xml_parser)), 12); - - xml_parser_free($xml_parser); - } - - /** - * Check GBX header, main class ID & header block - * @param Array $classes - * The main class IDs accepted for this GBX - * @return Size of GBX header block - */ - protected function checkHeader(array $classes) - { - // check magic header - $data = $this->readData(3); - $version = $this->readInt16(); - if ($data != 'GBX' || $version != 6) - $this->errorOut('No magic GBX header', 5); - - // check header block (un)compression - $data = $this->readData(4); - if ($data[1] != 'U') - $this->errorOut('Compressed GBX header block not supported', 6); - - // check main class ID - $mainClass = $this->readInt32(); - if (!in_array($mainClass, $classes)) - $this->errorOut(sprintf('Main class ID %08X not supported', $mainClass), 7); - $this->debugLog(sprintf('GBX main class ID: %08X', $mainClass)); - - // get header size - $headerSize = $this->readInt32(); - if ($headerSize == 0) - $this->errorOut('No GBX header block', 8); - - $this->debugLog(sprintf('GBX header block size: %d (%.1f KB)', - $headerSize, $headerSize / 1024)); - return $headerSize; - } // checkHeader - - /** - * Get list of chunks from GBX header block - * @param Int $headerSize - * Size of header block (chunks list & chunks data) - * @param array $chunks - * List of chunk IDs & names - * @return List of chunk offsets & sizes - */ - protected function getChunksList($headerSize, array $chunks) - { - // get number of chunks - $numChunks = $this->readInt32(); - if ($numChunks == 0) - $this->errorOut('No GBX header chunks', 9); - - $this->debugLog('GBX number of header chunks: ' . $numChunks); - $chunkStart = $this->getGBXptr(); - $this->debugLog(sprintf('GBX start of chunk list: 0x%04X', $chunkStart)); - $chunkOffset = $chunkStart + $numChunks * 8; - - // get list of all chunks - $chunksList = array(); - for ($i = 0; $i < $numChunks; $i++) - { - $chunkId = $this->readInt32(); - $chunkSize = $this->readInt32(); - - $chunkId &= 0x00000FFF; - $chunkSize &= 0x7FFFFFFF; - - if (array_key_exists($chunkId, $chunks)) { - $name = $chunks[$chunkId]; - $chunksList[$name] = array( - 'off' => $chunkOffset, - 'size' => $chunkSize - ); - } else { - $name = 'UNKNOWN'; - } - $this->debugLog(sprintf('GBX chunk %2d %-8s Id 0x%03X Offset 0x%06X Size %6d', - $i, $name, $chunkId, $chunkOffset, $chunkSize)); - $chunkOffset += $chunkSize; - } - - //$this->debugLog(print_r($chunksList, true)); - $totalSize = $chunkOffset - $chunkStart + 4; // numChunks - if ($headerSize != $totalSize) - $this->errorOut(sprintf('Chunk list size mismatch: %d <> %d', - $headerSize, $totalSize), 10); - - return $chunksList; - } // getChunksList - - /** - * Initialize for a new chunk - * @param int $offset - */ - protected function initChunk($offset) - { - $this->setGBXptr($offset); - $this->clearLookbacks(); - } - - /** - * Get XML chunk from GBX header block & optionally parse it - * @param array $chunksList - * List of chunk offsets & sizes - */ - protected function getXMLChunk(array $chunksList) - { - if (!isset($chunksList['XML'])) return; - - $this->initChunk($chunksList['XML']['off']); - $this->xml = $this->readString(); - - if ($chunksList['XML']['size'] != strlen($this->xml) + 4) - $this->errorOut(sprintf('XML chunk size mismatch: %d <> %d', - $chunksList['XML']['size'], strlen($this->xml) + 4), 11); - - if ($this->parseXml && $this->xml != '') - $this->parseXMLstring(); - } // getXMLChunk - - /** - * Get Author fields from GBX header block - */ - protected function getAuthorFields() - { - $this->authorVer = $this->readInt32(); - $this->authorLogin = $this->readString(); - $this->authorNick = $this->stripBOM($this->readString()); - $this->authorZone = $this->stripBOM($this->readString()); - $this->authorEInfo = $this->readString(); - } // getAuthorFields - - /** - * Get Author chunk from GBX header block - * @param array $chunksList - * List of chunk offsets & sizes - */ - protected function getAuthorChunk(array $chunksList) - { - if (!isset($chunksList['Author'])) return; - - $this->initChunk($chunksList['Author']['off']); - $version = $this->readInt32(); - $this->debugLog('GBX Author chunk version: ' . $version); - - $this->getAuthorFields(); - } // getAuthorChunk - -} // class GBXBaseFetcher - - -/** - * @class GBXChallMapFetcher - * @brief The class that fetches all GBX challenge/map info - */ -class GBXChallMapFetcher extends GBXBaseFetcher -{ - public $tnImage; - - public $headerVersn, $bronzeTime, $silverTime, $goldTime, $authorTime, - $cost, $multiLap, $type, $typeName, $authorScore, $simpleEdit, - $nbChecks, $nbLaps; - public $uid, $envir, $author, $name, $kind, $kindName, $password, - $mood, $envirBg, $authorBg, $mapType, $mapStyle, $lightmap, $titleUid; - public $xmlVer, $exeVer, $exeBld, $validated, $songFile, $songUrl, - $modName, $modFile, $modUrl; - public $thumbLen, $thumbnail, $comment; - - const IMAGE_FLIP_HORIZONTAL = 1; - const IMAGE_FLIP_VERTICAL = 2; - const IMAGE_FLIP_BOTH = 3; - - /** - * Mirror (flip) an image across horizontal, vertical or both axis - * Source: http://www.php.net/manual/en/function.imagecopy.php#85992 - * @param String $imgsrc - * Source image data - * @param Int $dir - * Flip direction from the constants above - * @return Flipped image data if successful, otherwise source image data - */ - private function imageFlip($imgsrc, $dir) - { - $width = imagesx($imgsrc); - $height = imagesy($imgsrc); - $src_x = 0; - $src_y = 0; - $src_width = $width; - $src_height = $height; - - switch ((int)$dir) { - case self::IMAGE_FLIP_HORIZONTAL: - $src_y = $height; - $src_height = -$height; - break; - case self::IMAGE_FLIP_VERTICAL: - $src_x = $width; - $src_width = -$width; - break; - case self::IMAGE_FLIP_BOTH: - $src_x = $width; - $src_y = $height; - $src_width = -$width; - $src_height = -$height; - break; - default: - return $imgsrc; - } - - $imgdest = imagecreatetruecolor($width, $height); - if (imagecopyresampled($imgdest, $imgsrc, 0, 0, $src_x, $src_y, - $width, $height, $src_width, $src_height)) { - return $imgdest; - } - return $imgsrc; - } // imageFlip - - /** - * Instantiate GBX challenge/map fetcher - * - * @param Boolean $parsexml - * If true, the fetcher also parses the XML block - * @param Boolean $tnimage - * If true, the fetcher also extracts the thumbnail image; - * if GD/JPEG libraries are present, image will be flipped upright, - * otherwise it will be in the original upside-down format - * Warning: this is binary data in JPEG format, 256x256 pixels for - * TMU/TMF or 512x512 pixels for MP - * @param Boolean $debug - * If true, the fetcher prints debug logging to stderr - * @return GBXChallMapFetcher - * If GBX data couldn't be extracted, an Exception is thrown with - * the error message & code - */ - public function __construct($parsexml = false, $tnimage = false, $debug = false) - { - parent::__construct(); - - $this->headerVersn = 0; - $this->bronzeTime = 0; - $this->silverTime = 0; - $this->goldTime = 0; - $this->authorTime = 0; - - $this->cost = 0; - $this->multiLap = false; - $this->type = 0; - $this->typeName = ''; - - $this->authorScore = 0; - $this->simpleEdit = false; - $this->nbChecks = 0; - $this->nbLaps = 0; - - $this->uid = ''; - $this->envir = ''; - $this->author = ''; - $this->name = ''; - $this->kind = 0; - $this->kindName = ''; - - $this->password = ''; - $this->mood = ''; - $this->envirBg = ''; - $this->authorBg = ''; - - $this->mapType = ''; - $this->mapStyle = ''; - $this->lightmap = 0; - $this->titleUid = ''; - - $this->xmlVer = ''; - $this->exeVer = ''; - $this->exeBld = ''; - $this->validated = false; - $this->songFile = ''; - $this->songUrl = ''; - $this->modName = ''; - $this->modFile = ''; - $this->modUrl = ''; - - $this->thumbLen = 0; - $this->thumbnail = ''; - $this->comment = ''; - - $this->parseXml = (bool)$parsexml; - $this->tnImage = (bool)$tnimage; - if ((bool)$debug) - $this->enableDebug(); - - $this->setError('GBX map error: '); - } // __construct - - /** - * Process GBX challenge/map file - * - * @param String $filename - * The challenge filename - */ - public function processFile($filename) - { - $this->loadGBXdata((string)$filename); - - $this->processGBX(); - } // processFile - - /** - * Process GBX challenge/map data - * - * @param String $gbxdata - * The challenge/map data - */ - public function processData($gbxdata) - { - $this->storeGBXdata((string)$gbxdata); - - $this->processGBX(); - } // processData - - // process GBX data - private function processGBX() - { - // supported challenge/map class IDs - $challclasses = array( - self::GBX_CHALLENGE_TMF, - self::GBX_CHALLENGE_TM, - ); - - $headerSize = $this->checkHeader($challclasses); - $headerStart = $headerEnd = $this->getGBXptr(); - - // desired challenge/map chunk IDs - $chunks = array( - 0x002 => 'Info', // TM, MP - 0x003 => 'String', // TM, MP - 0x004 => 'Version', // TM, MP - 0x005 => 'XML', // TM, MP - 0x007 => 'Thumbnl', // TM, MP - 0x008 => 'Author', // MP - ); - - $chunksList = $this->getChunksList($headerSize, $chunks); - - $this->getInfoChunk($chunksList); - $headerEnd = max($headerEnd, $this->getGBXptr()); - - $this->getStringChunk($chunksList); - $headerEnd = max($headerEnd, $this->getGBXptr()); - - $this->getVersionChunk($chunksList); - $headerEnd = max($headerEnd, $this->getGBXptr()); - - $this->getXMLChunk($chunksList); - $headerEnd = max($headerEnd, $this->getGBXptr()); - - $this->getThumbnlChunk($chunksList); - $headerEnd = max($headerEnd, $this->getGBXptr()); - - $this->getAuthorChunk($chunksList); - $headerEnd = max($headerEnd, $this->getGBXptr()); - - if ($headerSize != $headerEnd - $headerStart) - $this->errorOut(sprintf('Header size mismatch: %d <> %d', - $headerSize, $headerEnd - $headerStart), 16); - - if ($this->parseXml) { - if (isset($this->xmlParsed['HEADER']['VERSION'])) - $this->xmlVer = $this->xmlParsed['HEADER']['VERSION']; - if (isset($this->xmlParsed['HEADER']['EXEVER'])) - $this->exeVer = $this->xmlParsed['HEADER']['EXEVER']; - if (isset($this->xmlParsed['HEADER']['EXEBUILD'])) - $this->exeBld = $this->xmlParsed['HEADER']['EXEBUILD']; - if ($this->lightmap == 0 && isset($this->xmlParsed['HEADER']['LIGHTMAP'])) - $this->lightmap = (int)$this->xmlParsed['HEADER']['LIGHTMAP']; - if ($this->authorZone == '' && isset($this->xmlParsed['IDENT']['AUTHORZONE'])) - $this->authorZone = $this->xmlParsed['IDENT']['AUTHORZONE']; - if ($this->envir == 'UNKNOWN' && isset($this->xmlParsed['DESC']['ENVIR'])) - $this->envir = $this->xmlParsed['DESC']['ENVIR']; - if ($this->nbLaps == 0 && isset($this->xmlParsed['DESC']['NBLAPS'])) - $this->nbLaps = (int)$this->xmlParsed['DESC']['NBLAPS']; - if (isset($this->xmlParsed['DESC']['VALIDATED'])) - $this->validated = (bool)$this->xmlParsed['DESC']['VALIDATED']; - if (isset($this->xmlParsed['DESC']['MOD'])) - $this->modName = $this->xmlParsed['DESC']['MOD']; - - // extract optional song & mod filenames - if (!empty($this->xmlParsed['DEPS'])) { - for ($i = 0; $i < count($this->xmlParsed['DEPS']); $i++) { - if (preg_match('/ChallengeMusics\\\\(.+)/', $this->xmlParsed['DEPS'][$i]['FILE'], $path)) { - $this->songFile = $path[1]; - if (isset($this->xmlParsed['DEPS'][$i]['URL'])) - $this->songUrl = $this->xmlParsed['DEPS'][$i]['URL']; - } elseif (preg_match('/.+\\\\Mod\\\\.+/', $this->xmlParsed['DEPS'][$i]['FILE'], $path)) { - $this->modFile = $path[0]; - if (isset($this->xmlParsed['DEPS'][$i]['URL'])) - $this->modUrl = $this->xmlParsed['DEPS'][$i]['URL']; - } - } - } - } - - $this->clearGBXdata(); - } // processGBX - - /** - * Get Info chunk from GBX header block - * @param array $chunksList - * List of chunk offsets & sizes - */ - protected function getInfoChunk(array $chunksList) - { - if (!isset($chunksList['Info'])) return; - - $this->initChunk($chunksList['Info']['off']); - $version = $this->readInt8(); - $this->debugLog('GBX Info chunk version: ' . $version); - - if ($version < 3) { - $this->uid = $this->readLookbackString(); - - $this->envir = $this->readLookbackString(); - $this->author = $this->readLookbackString(); - - $this->name = $this->stripBOM($this->readString()); - } - $this->moveGBXptr(4); // skip bool 0 - - if ($version >= 1) { - $this->bronzeTime = $this->readInt32(); - - $this->silverTime = $this->readInt32(); - - $this->goldTime = $this->readInt32(); - - $this->authorTime = $this->readInt32(); - - if ($version == 2) - $this->moveGBXptr(1); // skip unknown byte - - if ($version >= 4) { - $this->cost = $this->readInt32(); - - if ($version >= 5) { - $this->multiLap = (bool)$this->readInt32(); - - if ($version == 6) - $this->moveGBXptr(4); // skip unknown bool - - if ($version >= 7) { - $this->type = $this->readInt32(); - switch ($this->type) { - case 0: $this->typeName = 'Race'; - break; - case 1: $this->typeName = 'Platform'; - break; - case 2: $this->typeName = 'Puzzle'; - break; - case 3: $this->typeName = 'Crazy'; - break; - case 4: $this->typeName = 'Shortcut'; - break; - case 5: $this->typeName = 'Stunts'; - break; - case 6: $this->typeName = 'Script'; - break; - default: $this->typeName = 'UNKNOWN'; - } - - if ($version >= 9) { - $this->moveGBXptr(4); // skip int32 0 - - if ($version >= 10) { - $this->authorScore = $this->readInt32(); - - if ($version >= 11) { - $this->simpleEdit = (bool)$this->readInt32(); - - if ($version >= 12) { - $this->moveGBXptr(4); // skip bool 0 - - if ($version >= 13) { - $this->nbChecks = $this->readInt32(); - - $this->nbLaps = $this->readInt32(); - } - } - } - } - } - } - } - } - } - } // getInfoChunk - - /** - * Get String chunk from GBX header block - * @param array $chunksList - * List of chunk offsets & sizes - */ - protected function getStringChunk(array $chunksList) - { - if (!isset($chunksList['String'])) return; - - $this->initChunk($chunksList['String']['off']); - $version = $this->readInt8(); - $this->debugLog('GBX String chunk version: ' . $version); - - $this->uid = $this->readLookbackString(); - - $this->envir = $this->readLookbackString(); - $this->author = $this->readLookbackString(); - - $this->name = $this->stripBOM($this->readString()); - - $this->kind = $this->readInt8(); - switch ($this->kind) { - case 0: $this->kindName = '(internal)EndMarker'; - break; - case 1: $this->kindName = '(old)Campaign'; - break; - case 2: $this->kindName = '(old)Puzzle'; - break; - case 3: $this->kindName = '(old)Retro'; - break; - case 4: $this->kindName = '(old)TimeAttack'; - break; - case 5: $this->kindName = '(old)Rounds'; - break; - case 6: $this->kindName = 'InProgress'; - break; - case 7: $this->kindName = 'Campaign'; - break; - case 8: $this->kindName = 'Multi'; - break; - case 9: $this->kindName = 'Solo'; - break; - case 10: $this->kindName = 'Site'; - break; - case 11: $this->kindName = 'SoloNadeo'; - break; - case 12: $this->kindName = 'MultiNadeo'; - break; - default: $this->kindName = 'UNKNOWN'; - } - - if ($version >= 1) { - $this->moveGBXptr(4); // skip locked - - $this->password = $this->readString(); - - if ($version >= 2) { - $this->mood = $this->readLookbackString(); - $this->mood = preg_replace('/([A-Za-z]+)\d*/', '\1', $this->mood); - - $this->envirBg = $this->readLookbackString(); - $this->authorBg = $this->readLookbackString(); - - if ($version >= 3) { - $this->moveGBXptr(8); // skip mapOrigin - - if ($version >= 4) { - $this->moveGBXptr(8); // skip mapTarget - - if ($version >= 5) { - $this->moveGBXptr(16); // skip unknown int128 - - if ($version >= 6) { - $this->mapType = $this->readString(); - $this->mapStyle = $this->readString(); - - if ($version <= 8) - $this->moveGBXptr(4); // skip unknown bool - - if ($version >= 8) { - $this->moveGBXptr(8); // skip lightmapCacheUID - - if ($version >= 9) { - $this->lightmap = $this->readInt8(); - - if ($version >= 11) { - $this->titleUid = $this->readLookbackString(); - } - } - } - } - } - } - } - } - } - } // getStringChunk - - /** - * Get Version chunk from GBX header block - * @param array $chunksList - * List of chunk offsets & sizes - */ - protected function getVersionChunk(array $chunksList) - { - if (!isset($chunksList['Version'])) return; - - $this->initChunk($chunksList['Version']['off']); - $this->headerVersn = $this->readInt32(); - } // getVersionChunk - - /** - * Get Thumbnail/Comments chunk from GBX header block - * @param array $chunksList - * List of chunk offsets & sizes - */ - protected function getThumbnlChunk(array $chunksList) - { - if (!isset($chunksList['Thumbnl'])) return; - - $this->initChunk($chunksList['Thumbnl']['off']); - $version = $this->readInt32(); - $this->debugLog('GBX Thumbnail chunk version: ' . $version); - - if ($version == 1) { - $thumbSize = $this->readInt32(); - $this->debugLog(sprintf('GBX Thumbnail size: %d (%.1f KB)', - $thumbSize, $thumbSize / 1024)); - - $this->moveGBXptr(strlen('')); - $this->thumbnail = $this->readData($thumbSize); - $this->thumbLen = strlen($this->thumbnail); - $this->moveGBXptr(strlen('')); - - $this->moveGBXptr(strlen('')); - $this->comment = $this->stripBOM($this->readString()); - $this->moveGBXptr(strlen('')); - - // return extracted thumbnail image? - if ($this->tnImage && $this->thumbLen > 0) { - // check for GD/JPEG libraries - if (function_exists('imagecreatefromjpeg') && - function_exists('imagecopyresampled')) { - // flip thumbnail via temporary file - $tmp = tempnam(sys_get_temp_dir(), 'gbxflip'); - if (@file_put_contents($tmp, $this->thumbnail) !== false) { - if ($tn = @imagecreatefromjpeg($tmp)) { - $tn = $this->imageFlip($tn, self::IMAGE_FLIP_HORIZONTAL); - if (@imagejpeg($tn, $tmp)) { - if (($tn = @file_get_contents($tmp)) !== false) { - $this->thumbnail = $tn; - } - } - } - unlink($tmp); - } - } - } else { - $this->thumbnail = ''; - } - } - } // getThumbnlChunk - -} // class GBXChallMapFetcher - - -/** - * @class GBXChallengeFetcher - * @brief Wrapper class for backwards compatibility with the old GBXChallengeFetcher - * @deprecated Do not use for new development, use GBXChallMapFetcher instead - */ -class GBXChallengeFetcher extends GBXChallMapFetcher -{ - public $authortm, $goldtm, $silvertm, $bronzetm, $ascore, $azone, $multi, $editor, - $pub, $nblaps, $parsedxml, $xmlver, $exever, $exebld, $songfile, $songurl, - $modname, $modfile, $modurl; - - /** - * Fetches a hell of a lot of data about a GBX challenge - * - * @param String $filename - * The challenge filename (must include full path) - * @param Boolean $parsexml - * If true, the script also parses the XML block - * @param Boolean $tnimage - * If true, the script also extracts the thumbnail image; if GD/JPEG - * libraries are present, image will be flipped upright, otherwise - * it will be in the original upside-down format - * Warning: this is binary data in JPEG format, 256x256 pixels for - * TMU/TMF or 512x512 pixels for MP - * @return GBXChallengeFetcher - * If $uid is empty, GBX data couldn't be extracted - */ - public function __construct($filename, $parsexml = false, $tnimage = false) - { - parent::__construct($parsexml, $tnimage, false); - - try - { - $this->processFile($filename); - - $this->authortm = $this->authorTime; - $this->goldtm = $this->goldTime; - $this->silvertm = $this->silverTime; - $this->bronzetm = $this->bronzeTime; - $this->ascore = $this->authorScore; - $this->azone = $this->authorZone; - $this->multi = $this->multiLap; - $this->editor = $this->simpleEdit; - $this->pub = $this->authorBg; - $this->nblaps = $this->nbLaps; - $this->parsedxml = $this->xmlParsed; - $this->xmlver = $this->xmlVer; - $this->exever = $this->exeVer; - $this->exebld = $this->exeBld; - $this->songfile = $this->songFile; - $this->songurl = $this->songUrl; - $this->modname = $this->modName; - $this->modfile = $this->modFile; - $this->modurl = $this->modUrl; - } - catch (Exception $e) - { - $this->uid = ''; - } - } - -} // class GBXChallengeFetcher - - -/** - * @class GBXReplayFetcher - * @brief The class that fetches all GBX replay info - * @note The interface for GBXReplayFetcher has changed compared to the old class, - * but there is no wrapper because no third-party XASECO[2] plugins used that - */ -class GBXReplayFetcher extends GBXBaseFetcher -{ - public $uid, $envir, $author, $replay, $nickname, $login, $titleUid; - public $xmlVer, $exeVer, $exeBld, $respawns, $stuntScore, $validable, - $cpsCur, $cpsLap; - - /** - * Instantiate GBX replay fetcher - * - * @param Boolean $parsexml - * If true, the fetcher also parses the XML block - * @param Boolean $debug - * If true, the fetcher prints debug logging to stderr - * @return GBXReplayFetcher - * If GBX data couldn't be extracted, an Exception is thrown with - * the error message & code - */ - public function __construct($parsexml = false, $debug = false) - { - parent::__construct(); - - $this->uid = ''; - $this->envir = ''; - $this->author = ''; - $this->replay = 0; - $this->nickname = ''; - $this->login = ''; - $this->titleUid = ''; - - $this->xmlVer = ''; - $this->exeVer = ''; - $this->exeBld = ''; - $this->respawns = 0; - $this->stuntScore = 0; - $this->validable = false; - $this->cpsCur = 0; - $this->cpsLap = 0; - - $this->parseXml = (bool)$parsexml; - if ((bool)$debug) - $this->enableDebug(); - - $this->setError('GBX replay error: '); - } // __construct - - /** - * Process GBX replay file - * - * @param String $filename - * The replay filename - */ - public function processFile($filename) - { - $this->loadGBXdata((string)$filename); - - $this->processGBX(); - } // processFile - - /** - * Process GBX replay data - * - * @param String $gbxdata - * The replay data - */ - public function processData($gbxdata) - { - $this->storeGBXdata((string)$gbxdata); - - $this->processGBX(); - } // processData - - // process GBX data - private function processGBX() - { - // supported replay class IDs - $replayclasses = array( - self::GBX_AUTOSAVE_TMF, - self::GBX_AUTOSAVE_TM, - self::GBX_REPLAY_TM, - ); - - $headerSize = $this->checkHeader($replayclasses); - $headerStart = $headerEnd = $this->getGBXptr(); - - // desired replay chunk IDs - $chunks = array( - 0x000 => 'String', // TM, MP - 0x001 => 'XML', // TM, MP - 0x002 => 'Author', // MP - ); - - $chunksList = $this->getChunksList($headerSize, $chunks); - - $this->getStringChunk($chunksList); - $headerEnd = max($headerEnd, $this->getGBXptr()); - - $this->getXMLChunk($chunksList); - $headerEnd = max($headerEnd, $this->getGBXptr()); - - $this->getAuthorChunk($chunksList); - $headerEnd = max($headerEnd, $this->getGBXptr()); - - if ($headerSize != $headerEnd - $headerStart) - $this->errorOut(sprintf('Header size mismatch: %d <> %d', - $headerSize, $headerEnd - $headerStart), 20); - - if ($this->parseXml) { - if (isset($this->xmlParsed['HEADER']['VERSION'])) - $this->xmlVer = $this->xmlParsed['HEADER']['VERSION']; - if (isset($this->xmlParsed['HEADER']['EXEVER'])) - $this->exeVer = $this->xmlParsed['HEADER']['EXEVER']; - if (isset($this->xmlParsed['HEADER']['EXEBUILD'])) - $this->exeBld = $this->xmlParsed['HEADER']['EXEBUILD']; - if (isset($this->xmlParsed['TIMES']['RESPAWNS'])) - $this->respawns = (int)$this->xmlParsed['TIMES']['RESPAWNS']; - if (isset($this->xmlParsed['TIMES']['STUNTSCORE'])) - $this->stuntScore = (int)$this->xmlParsed['TIMES']['STUNTSCORE']; - if (isset($this->xmlParsed['TIMES']['VALIDABLE'])) - $this->validable = (bool)$this->xmlParsed['TIMES']['VALIDABLE']; - if (isset($this->xmlParsed['CHECKPOINTS']['CUR'])) - $this->cpsCur = (int)$this->xmlParsed['CHECKPOINTS']['CUR']; - if (isset($this->xmlParsed['CHECKPOINTS']['ONELAP'])) - $this->cpsLap = (int)$this->xmlParsed['CHECKPOINTS']['ONELAP']; - } - - $this->clearGBXdata(); - } // processGBX - - /** - * Get String chunk from GBX header block - * @param array $chunksList - * List of chunk offsets & sizes - */ - protected function getStringChunk(array $chunksList) - { - if (!isset($chunksList['String'])) return; - - $this->initChunk($chunksList['String']['off']); - $version = $this->readInt32(); - $this->debugLog('GBX String chunk version: ' . $version); - - if ($version >= 2) { - $this->uid = $this->readLookbackString(); - - $this->envir = $this->readLookbackString(); - $this->author = $this->readLookbackString(); - - $this->replay = $this->readInt32(); - - $this->nickname = $this->stripBOM($this->readString()); - - if ($version >= 6) { - $this->login = $this->readString(); - - if ($version >= 8) { - $this->moveGBXptr(1); // skip unknown byte - - $this->titleUid = $this->readLookbackString(); - } - } - } - } // getStringChunk - -} // class GBXReplayFetcher - - -/** - * @class GBXPackFetcher - * @brief The class that fetches all GBX pack info - */ -class GBXPackFetcher extends GBXBaseFetcher -{ - public $headerVersn, $flags, $infoMlUrl, $creatDate, $comment, $titleId, - $usageSubdir, $buildInfo, $authorUrl, $exeVer, $exeBld, $xmlDate; - - /** - * Read Windows FileTime and convert to Unix timestamp - * Filetime = 64-bit value with the number of 100-nsec intervals since Jan 1, 1601 (UTC) - * Based on http://www.mysqlperformanceblog.com/2007/03/27/integers-in-php-running-with-scissors-and-portability/ - * @return Unix timestamp, or -1 on error - */ - private function readFiletime() - { - // Unix epoch (1970-01-01) - Windows epoch (1601-01-01) in 100ns units - $EPOCHDIFF = '116444735995904000'; - $UINT32MAX = '4294967296'; - $USEC2SEC = 1000000; - - $lo = $this->readInt32(); - $hi = $this->readInt32(); - - // check for 64-bit platform - if (PHP_INT_SIZE >= 8) { - // use native math - if ($lo < 0) $lo += (1 << 32); - $date = ($hi << 32) + $lo; - $this->debugLog(sprintf('PAK CreationDate source: %016x', $date)); - if ($date == 0) return -1; - - // convert to Unix timestamp in usec - $stamp = ($date - (int)$EPOCHDIFF) / 10; - $this->debugLog(sprintf('PAK CreationDate 64-bit: %u.%06u', - $stamp / $USEC2SEC, $stamp % $USEC2SEC)); - return (int)($stamp / $USEC2SEC); - - // check for 32-bit platform - } elseif (PHP_INT_SIZE >= 4) { - $this->debugLog(sprintf('PAK CreationDate source: %08x%08x', $hi, $lo)); - if ($lo == 0 && $hi == 0) return -1; - - // workaround signed/unsigned braindamage on x32 - $lo = sprintf('%u', $lo); - $hi = sprintf('%u', $hi); - - // try and use GMP - if (function_exists('gmp_mul')) { - $date = gmp_add(gmp_mul($hi, $UINT32MAX), $lo); - // convert to Unix timestamp in usec - $stamp = gmp_div(gmp_sub($date, $EPOCHDIFF), 10); - $stamp = gmp_div_qr($stamp, $USEC2SEC); - $this->debugLog(sprintf('PAK CreationDate GNU MP: %u.%06u', - gmp_strval($stamp[0]), gmp_strval($stamp[1]))); - return (int)gmp_strval($stamp[0]); - } - - // try and use BC Math - if (function_exists('bcmul')) { - $date = bcadd(bcmul($hi, $UINT32MAX), $lo); - // convert to Unix timestamp in usec - $stamp = bcdiv(bcsub($date, $EPOCHDIFF), 10, 0); - $this->debugLog(sprintf('PAK CreationDate BCMath: %u.%06u', - bcdiv($stamp, $USEC2SEC), bcmod($stamp, $USEC2SEC))); - return (int)bcdiv($stamp, $USEC2SEC); - } - - // compute everything manually - $a = substr($hi, 0, -5); - $b = substr($hi, -5); - // hope that float precision is enough - $ac = $a * 42949; - $bd = $b * 67296; - $adbc = $a * 67296 + $b * 42949; - $r4 = substr($bd, -5) + substr($lo, -5); - $r3 = substr($bd, 0, -5) + substr($adbc, -5) + substr($lo, 0, -5); - $r2 = substr($adbc, 0, -5) + substr($ac, -5); - $r1 = substr($ac, 0, -5); - while ($r4 >= 100000) { $r4 -= 100000; $r3++; } - while ($r3 >= 100000) { $r3 -= 100000; $r2++; } - while ($r2 >= 100000) { $r2 -= 100000; $r1++; } - $date = ltrim(sprintf('%d%05d%05d%05d', $r1, $r2, $r3, $r4), '0'); - - // convert to Unix timestamp in usec - $r3 = substr($date, -6) - substr($EPOCHDIFF, -6); - $r2 = substr($date, -12, 6) - substr($EPOCHDIFF, -12, 6); - $r1 = substr($date, -18, 6) - substr($EPOCHDIFF, -18, 6); - if ($r3 < 0) { $r3 += 1000000; $r2--; } - if ($r2 < 0) { $r2 += 1000000; $r1--; } - $stamp = substr(sprintf('%d%06d%06d', $r1, $r2, $r3), 0, -1); - $this->debugLog(sprintf('PAK CreationDate manual: %s.%s', - substr($stamp, 0, -6), substr($stamp, -6))); - return (int)substr($stamp, 0, -6); - } else { - return -1; - } - } - - /** - * Instantiate GBX pack fetcher - * - * @param Boolean $parsexml - * If true, the fetcher also parses the XML block - * @param Boolean $debug - * If true, the fetcher prints debug logging to stderr - * @return GBXPackFetcher - * If GBX data couldn't be extracted, an Exception is thrown with - * the error message & code - */ - public function __construct($parsexml = false, $debug = false) - { - parent::__construct(); - - $this->headerVersn = 0; - $this->flags = 0; - $this->infoMlUrl = ''; - $this->creatDate = -1; - $this->comment = ''; - $this->titleId = ''; - $this->usageSubdir = ''; - $this->buildInfo = ''; - $this->authorUrl = ''; - $this->exeVer = ''; - $this->exeBld = ''; - $this->xmlDate = ''; - - $this->parseXml = (bool)$parsexml; - if ((bool)$debug) - $this->enableDebug(); - - $this->setError('GBX pack error: '); - } // __construct - - /** - * Process GBX pack file - * - * @param String $filename - * The pack filename - */ - public function processFile($filename) - { - $this->loadGBXdata((string)$filename); - - $this->processGBX(); - } // processFile - - /** - * Process GBX pack data - * - * @param String $gbxdata - * The pack data - */ - public function processData($gbxdata) - { - $this->storeGBXdata((string)$gbxdata); - - $this->processGBX(); - } // processData - - // process GBX data - private function processGBX() - { - // check magic header - $data = $this->readData(8); - if ($data != 'NadeoPak') - $this->errorOut('No magic NadeoPak header', 5); - - $this->headerVersn = $this->readInt32(); - if ($this->headerVersn < 6) - $this->errorOut(sprintf('Pack version %d not supported', $this->headerVersn), 24); - - $this->moveGBXptr(32); // skip ContentsChecksum - - $this->flags = $this->readInt32(); - - if ($this->headerVersn >= 7) { - $this->getAuthorFields(); - - if ($this->headerVersn < 9) { - $this->comment = $this->stripBOM($this->readString()); - - $this->moveGBXptr(16); // skip unused uint128 - - if ($this->headerVersn >= 8) { - $this->buildInfo = $this->readString(); - - $this->authorUrl = $this->readString(); - } - - } else { // >= 9 - $this->infoMlUrl = $this->readString(); - - $this->creatDate = $this->readFiletime(); - - $this->comment = $this->stripBOM($this->readString()); - - if ($this->headerVersn >= 12) { - $this->xml = $this->readString(); - $this->titleId = $this->readString(); - - if ($this->parseXml && $this->xml != '') { - $this->parseXMLstring(); - - if (isset($this->xmlParsed['HEADER']['EXEVER'])) - $this->exeVer = $this->xmlParsed['HEADER']['EXEVER']; - if (isset($this->xmlParsed['HEADER']['EXEBUILD'])) - $this->exeBld = $this->xmlParsed['HEADER']['EXEBUILD']; - if (isset($this->xmlParsed['IDENT']['CREATIONDATE'])) - $this->xmlDate = $this->xmlParsed['IDENT']['CREATIONDATE']; - } - } - - $this->usageSubdir = $this->readString(); - - $this->buildInfo = $this->readString(); - - $this->moveGBXptr(16); // skip unused uint128 - // if ($this->headerVersn >= 10) // skip encrypted IncludedPacks - } - } - - $this->clearGBXdata(); - } // processGBX - -} // class GBXPackFetcher -