private function parseDocumentHeader($string) { $numByte = 44; // skip header and unknown stuff $numDeps = MPQFile::readByte($string, $numByte); // uncertain that this is the number of dependencies, might also be uint32 if it is $numByte += 3; while ($numDeps > 0) { while (MPQFile::readByte($string, $numByte) !== 0) { } $numDeps--; } $numAttribs = MPQFile::readUInt32($string, $numByte); $attribs = array(); while ($numAttribs > 0) { $keyLen = MPQFile::readUInt16($string, $numByte); $key = MPQFile::readBytes($string, $numByte, $keyLen); $numByte += 4; // always seems to be followed by ascii SUne $valueLen = MPQFile::readUInt16($string, $numByte); $value = MPQFile::readBytes($string, $numByte, $valueLen); $attribs[$key] = $value; $numAttribs--; } $this->author = $attribs["DocInfo/Author"]; $this->mapName = $attribs["DocInfo/Name"]; $this->description = $attribs["DocInfo/DescLong"]; $this->shortDescription = $attribs["DocInfo/DescShort"]; }
public function addFakeObserver($obsName) { return false; // this function does not work currently so DO NOT USE! if (!$this->init || $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 0x4: case 0x2: // spectator // spectator case 0x0: // computer $numByte += 4; break; } break; case 0xc2: switch ($secondByte) { case 0x4: $tmp = MPQFile::readByte($string, $numByte); if ($tmp == 0x5) { $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, 0xb), 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 parseGameEventsFile_pre22612(&$string) { $len = strlen($string); $playerLeft = array(); $events = array(); $previousEventByte = 0; // start of the previous event's data location $time = 0; $numEvents = 0; $numByte = 0; $eventType = 0; $eventCode = 0; while ($numByte < $len) { $knownEvent = true; $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++; if ($globalEventFlag > 0 && $playerId > 0) { $knownEvent = false; } else { switch ($eventType) { case 0x0: // initialization switch ($eventCode) { case 0x2b: case 0x2c: case 0xc: // for build >= 17326 // for build >= 17326 case 0xb: // Player enters game if ($playerId == 0) { $knownEvent = false; } break; case 0x5: // game starts if ($globalEventFlag == 0 || $playerId > 0) { $knownEvent = false; } break; default: $knownEvent = false; } break; case 0x1: // action switch ($eventCode) { case 0x9: // player quits the game if ($this->players[$playerId]['team'] > 0) { // don't log observers/party members etc $playerLeft[] = $playerId; } break; case 0x1b: case 0x2b: case 0x3b: case 0x4b: case 0x5b: case 0x6b: case 0x7b: case 0x8b: case 0x9b: case 0xb: // player uses an ability $ability = -1; $firstByte = -1; if ($this->build >= 18317) { $firstByte = MPQFile::readByte($string, $numByte); $temp = MPQFile::readByte($string, $numByte); if ($firstByte & 0xc && !($firstByte & 1)) { if ($temp & 8) { if ($temp & 0x80) { $numByte += 8; if ($this->build >= 19595) { $numByte += 1; } } $numByte += 10; $ability = 0; } else { $ability = MPQFile::readByte($string, $numByte) << 16 | MPQFile::readByte($string, $numByte) << 8 | MPQFile::readByte($string, $numByte); if (($temp & 0x60) == 0x60) { $numByte += 4; } else { $flagtemp = $ability & 0xf0; // some kind of flag if ($flagtemp & 0x20) { $numByte += 9; if ($firstByte & 8) { $numByte += 9; } } else { if ($flagtemp & 0x10) { $numByte += 9; } else { if ($flagtemp & 0x40) { $numByte += 18; if ($this->build >= 19595) { $numByte += 1; } } } } } $ability = $ability & 0xffff0f; // strip flag bits } } } if ($this->build >= 16561 && ($firstByte == -1 || $ability == -1)) { if ($firstByte == -1) { $firstByte = MPQFile::readByte($string, $numByte); $temp = MPQFile::readByte($string, $numByte); } if ($ability == -1) { $ability = MPQFile::readByte($string, $numByte) << 16 | MPQFile::readByte($string, $numByte) << 8 | MPQFile::readByte($string, $numByte) & 0x3f; } if ($temp == 0x20 || $temp == 0x22) { $nByte = $ability & 0xff; if ($nByte > 0x7) { if ($firstByte == 0x29 || $firstByte == 0x19) { $numByte += 4; } else { $numByte += 9; if (($nByte & 0x20) > 0) { $numByte += 8; $nByte = MPQFile::readByte($string, $numByte); if ($nByte & 8) { $numByte += 4; } } } } } else { if ($temp == 0x48 || $temp == 0x4a) { $numByte += 7; } else { if ($temp == 0x88 || $temp == 0x8a) { $numByte += 15; } } } } // the following updates race name in English based on the worker (SCV, Drone, Probe) that the player trained first if ($this->build >= 16561) { if (!$this->players[$playerId]['isObs'] && $this->players[$playerId]['race'] == "") { if ($this->build >= 19679) { if ($ability == 0x10d00) { $this->players[$playerId]['race'] = "Terran"; } elseif ($ability == 0x12100) { $this->players[$playerId]['race'] = "Protoss"; } elseif ($ability == 0x13300) { $this->players[$playerId]['race'] = "Zerg"; } } if ($this->build >= 18574) { if ($ability == 0x10c00) { $this->players[$playerId]['race'] = "Terran"; } elseif ($ability == 0x12000) { $this->players[$playerId]['race'] = "Protoss"; } elseif ($ability == 0x13200) { $this->players[$playerId]['race'] = "Zerg"; } } else { if ($this->build >= 17326) { if ($ability == 0x20c00) { $this->players[$playerId]['race'] = "Terran"; } elseif ($ability == 0x22000) { $this->players[$playerId]['race'] = "Protoss"; } elseif ($ability == 0x23200) { $this->players[$playerId]['race'] = "Zerg"; } } else { if ($ability == 0x20a00) { $this->players[$playerId]['race'] = "Terran"; } elseif ($ability == 0x21e00) { $this->players[$playerId]['race'] = "Protoss"; } elseif ($ability == 0x23000) { $this->players[$playerId]['race'] = "Zerg"; } } } } if ($ability) { $this->addPlayerAbility($playerId, ceil($time / 16), $ability); $events[] = array('p' => $playerId, 't' => $time, 'a' => $ability); $this->events = $events; } if ($this->debug) { $this->debug(sprintf("Used ability - player id: {$playerId} - time: %d - ability code: %06X", floor($time / 16), $ability)); } $this->addPlayerAction($playerId, floor($time / 16)); break; } // the following section is only reached for builds pre-16561 $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 0x80a00: //SCV $this->players[$playerId]['race'] = "Terran"; break; case 0x90e00: //probe $this->players[$playerId]['race'] = "Protoss"; break; case 0xb0000: //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->addPlayerAbility($playerId, ceil($time / 16), $ability); break; case 0x2f: // player sends resources $numByte += 17; // data is 17 bytes long break; case 0xc: // automatic update of hotkey? // 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 // 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 // 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 // 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 // 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 // 01 05 10 01 01 10 02 01 01 1a a0 00 01 case 0x8c: case 0x9c: case 0xac: // player changes selection if ($this->build >= 16561) { $numByte++; // skip flag byte $deselectFlags = MPQFile::readByte($string, $numByte); if (($deselectFlags & 3) == 1) { $nextByte = MPQFile::readByte($string, $numByte); $deselectionBits = $deselectFlags & 0xfc | $nextByte & 3; while ($deselectionBits > 6) { $nextByte = MPQFile::readByte($string, $numByte); $deselectionBits -= 8; } $deselectionBits += 2; $deselectionBits = $deselectionBits % 8; $bitMask = pow(2, $deselectionBits) - 1; } else { if (($deselectFlags & 3) == 2 || ($deselectFlags & 3) == 3) { $nextByte = MPQFile::readByte($string, $numByte); $deselectionBytes = $deselectFlags & 0xfc | $nextByte & 3; while ($deselectionBytes > 0) { $nextByte = MPQFile::readByte($string, $numByte); $deselectionBytes--; } $bitMask = 3; } else { if (($deselectFlags & 3) == 0) { $bitMask = 3; $nextByte = $deselectFlags; } } } $uType = array(); $unitIDs = array(); $prevByte = $nextByte; $nextByte = MPQFile::readByte($string, $numByte); if ($bitMask > 0) { $numUnitTypeIDs = $prevByte & 0xff - $bitMask | $nextByte & $bitMask; } else { $numUnitTypeIDs = $nextByte; } for ($i = 0; $i < $numUnitTypeIDs; $i++) { $unitTypeID = 0; for ($j = 0; $j < 3; $j++) { $prevByte = $nextByte; $nextByte = MPQFile::readByte($string, $numByte); if ($bitMask > 0) { $byte = $prevByte & 0xff - $bitMask | $nextByte & $bitMask; } else { $byte = $nextByte; } $unitTypeID = $byte << (2 - $j) * 8 | $unitTypeID; } $prevByte = $nextByte; $nextByte = MPQFile::readByte($string, $numByte); if ($bitMask > 0) { $unitTypeCount = $prevByte & 0xff - $bitMask | $nextByte & $bitMask; } else { $unitTypeCount = $nextByte; } $uType[$i + 1]['count'] = $unitTypeCount; $uType[$i + 1]['id'] = $unitTypeID; } $prevByte = $nextByte; $nextByte = MPQFile::readByte($string, $numByte); if ($bitMask > 0) { $numUnits = $prevByte & 0xff - $bitMask | $nextByte & $bitMask; } else { $numUnits = $nextByte; } for ($i = 0; $i < $numUnits; $i++) { $unitID = 0; for ($j = 0; $j < 4; $j++) { $prevByte = $nextByte; $nextByte = MPQFile::readByte($string, $numByte); if ($bitMask > 0) { $byte = $prevByte & 0xff - $bitMask | $nextByte & $bitMask; } else { $byte = $nextByte; } if ($j < 2) { $unitID = $byte << (1 - $j) * 8 | $unitID; } } $unitIDs[] = $unitID; } $a = 0; if ($this->debug) { $this->debug("Selection Change"); $this->debug(sprintf("Player %s", $playerId)); } foreach ($uType as $unitType) { for ($i = 1; $i <= $unitType['count']; $i++) { $uid = $unitIDs[$a]; //Bytes 3 + 4 contain flag info (perhaps same as in 1.00) $this->addSelectedUnit($uid, $unitType['id'], $playerId, floor($time / 16)); if ($this->debug) { $this->debug(sprintf(" 0x%06X -> 0x%04X", $unitType['id'], $uid)); } $a++; } } if ($eventCode == 0xac) { $this->addPlayerAction($playerId, floor($time / 16)); } break; } $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 && $this->build < 16561) { // 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 0xd: // manually uses hotkey // 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 ($byte1 <= 3) { break; } if ($numByte < $len) { $byte2 = MPQFile::readByte($string, $numByte); $numByte--; } if ($this->build < 16561) { $tmp = 0; $extraBytes = floor($byte1 / 8); $numByte += $extraBytes; if ($byte1 & 4 && ($byte2 & 6) == 6) { $tmp = MPQFile::readByte($string, $numByte); $numByte++; } else { if ($byte1 & 4) { $tmp = MPQFile::readByte($string, $numByte); } } } if ($this->build >= 16561) { if ($byte1 & 8) { //if ($byte1 == 0x0A || $byte1 == 0x09) { $numByte += MPQFile::readByte($string, $numByte) & 0xf; break; } $extraBytes = floor($byte1 / 8); $numByte += $extraBytes; $tmp = MPQFile::readByte($string, $numByte); if ($extraBytes == 0) { if (($byte2 & 7) > 4) { $numByte++; } if ($byte2 & 8) { $numByte++; } } else { if ($byte1 & 4 && ($byte2 & 7) > 4) { $numByte++; } if ($byte1 & 4 && $byte2 & 8) { $numByte++; } //if ($byte1 & 8) $numByte += ($byte2 & 0x0F) - 1; } } if ($this->debug) { $this->debug(sprintf("Byte1: {$byte1}, Byte2: {$byte2}, Numbyte: %04X, Extra bytes: {$extraBytes}", $numByte)); } // update apm $this->addPlayerAction($playerId, floor($time / 16)); break; case 0x1f: // send resources // 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] & 0xf); // 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] & 0xf); // last 8 bytes are unknown $numByte += 8; break; default: $knownEvent = false; } break; case 0x2: // weird switch ($eventCode) { case 0x6: $numByte += 8; // 00 00 00 04 00 00 00 04 break; case 0x7: case 0xe: $numByte += 4; break; default: $knownEvent = false; } break; case 0x3: // replay switch ($eventCode) { case 0x87: $numByte += 8; break; // 64 d4 6b a4 b5 48 4d ff 43 fa 56 8d 67 1a 15 88 0f 04 00 00 00 00 00 00 00 00 00 00 6e 03 00 00 00 00 // c2 b6 c4 06 4d fa 56 8d 07 02 00 01 d3 08 00 00 6e 03 // the next event code format is based on the previous two lines, may be faulty // the purpose is unknown // 64 d4 6b a4 b5 48 4d ff 43 fa 56 8d 67 1a 15 88 0f 04 00 00 00 00 00 00 00 00 00 00 6e 03 00 00 00 00 // c2 b6 c4 06 4d fa 56 8d 07 02 00 01 d3 08 00 00 6e 03 // the next event code format is based on the previous two lines, may be faulty // the purpose is unknown case 0x8: $numByte += 3; $nextByte = MPQFile::readByte($string, $numByte); if (($nextByte & 0xf0) > 0) { $numByte += 4; } $numByte += 4; $nextByte = MPQFile::readByte($string, $numByte); if (($nextByte & 0xf0) > 0) { $numByte += 4; } $nextByte = MPQFile::readByte($string, $numByte); $numByte += ($nextByte & 0xf) * 4; // $numByte += 10; break; case 0x18: $numByte += 162; break; case 0x1: // camera movement // 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 // zoom camera up or down case 0x30: // only 0x10 matters, but due to 0x70 mask in comparison, check for this too // 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 // if non-zero (as in 0x40), rotate segment(2 bytes) follows case 0x40: // rotate camera $numByte += 2; } break; default: $knownEvent = false; } break; case 0x4: // inaction if (($eventCode & 0xf) == 2) { $numByte += 2; break; } else { if (($eventCode & 0xc) == 2) { break; } else { if (($eventCode & 0xc) == 0xc) { break; } } } // at least 0x1C, 0x3C, 0x4C and 0x7C have been observed switch ($eventCode) { case 0x16: $numByte += 24; break; case 0xc6: // unknown $numByte += 16; break; case 0x18: $numByte += 4; break; case 0x87: //unknown $numByte += 4; break; default: $knownEvent = false; } break; case 0x5: // system switch ($eventCode) { case 0x89: //automatic synchronization? $numByte += 4; break; default: $knownEvent = false; } break; default: $knownEvent = false; } } if ($knownEvent == false) { if ($this->debug) { $this->debug(sprintf("Unknown event: Timestamp: %d, Type: %d, Global: %d, Player ID: %d (%s), Event code: %02X Byte: %08X<br />\n", $timeStamp, $eventType, $globalEventFlag, $playerId, $playerName, $eventCode, $numByte)); } return false; } else { if ($this->debug >= 2) { $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)); } } } // in case ability codes change, populate empty 'race' array index to the locale-specific value $teamCounts = array(); foreach ($this->getActualPlayers() as $val) { if ($val['race'] == "") { $this->players[$val['id']]['race'] = $val['lrace']; } if ($this->recorderId > 0 && $val['id'] == $this->recorderId) { // if the recorder is a player, add him to the player left -array $playerLeft[] = $val['id']; } // for maximum accuracy in winner detection if (isset($teamCounts[$val['team']])) { $teamCounts[$val['team']]++; } else { $teamCounts[$val['team']] = 1; } if ($this->winnerTeam > 0) { if ($val['team'] == $this->winnerTeam) { $this->players[$val['id']]['won'] = 1; } else { $this->players[$val['id']]['won'] = 0; } } } // if the winner is known in replay.details ($this->winnerTeam > 0), skip the winner detection algorithm below if ($this->winnerTeam > 0) { $this->winnerKnown = true; return $numEvents; } $numLeft = count($playerLeft); $numActual = count($this->getActualPlayers()); $lastLeaver = -1; foreach ($playerLeft as $val) { $lastLeaver = $val; $teamCounts[$this->players[$val]['team']]--; } // at this point teams with 0 players are clearly the losers (with the exception below). // If there are two or more teams with 1 or more players still left, winner can't be determined. // If there is exactly one team with more than 0 players left, that team is the winner // If no teams have more than 0 players left, the last leaver's team is the winner. $tempWinnerTeam = 0; $winnerKnown = false; for ($i = 1; $i <= 15; $i++) { // maximum number of teams is 15 (ffa) if (!isset($teamCounts[$i])) { continue; } if ($teamCounts[$i] > 0 && $tempWinnerTeam == 0) { $winnerKnown = true; $tempWinnerTeam = $i; } else { if ($teamCounts[$i] > 0 && $tempWinnerTeam > 0) { $winnerKnown = false; break; } } // more than 1 team with 1 or more players left, winner undeterminable } if ($tempWinnerTeam == 0 && $lastLeaver > 0) { // this means that no team had more than 0 players left, so use the team of $lastLeaver $winnerKnown = true; $tempWinnerTeam = $this->players[$lastLeaver]['team']; } $this->winnerKnown = $winnerKnown; if ($winnerKnown) { foreach ($this->getActualPlayers() as $val) { if ($val['team'] == $tempWinnerTeam) { $this->players[$val['id']]['won'] = 1; } else { $this->players[$val['id']]['won'] = 0; } } } return $numEvents; }