/** * Analyzes body stream. * * @return bool true if the stream was successfully analyzed, false otherwise */ protected function analyzeBodyStreamF() { $time_cnt = $this->gameSettings->gameSpeed; $age_flag = array(0, 0, 0, 0, 0, 0, 0, 0); $bodyStream = $this->bodyStream->getDataString(); $gameInfo = $this->gameInfo; $size = $this->bodyStream->getSize(); $pos = 0; while ($pos < $size - 3) { if ($pos == 0 && !$this->isMgx) { $od_type = 0x4; } else { $packed_data = substr($bodyStream, $pos, 4); $pos += 4; $unpacked_data = unpack('l', $packed_data); $od_type = $unpacked_data[1]; } // ope_data types: 4(Game_start or Chat), 2(Sync), or 1(Command) switch ($od_type) { // Game_start or Chat command case 0x4: case 0x3: $packed_data = substr($bodyStream, $pos, 4); $pos += 4; $unpacked_data = unpack('l', $packed_data); $command = $unpacked_data[1]; if ($command == 0x1f4) { // Game_start if ($this->isMgl) { $pos += 28; $packed_data = substr($bodyStream, $pos, 1); $pos++; $unpacked_data = unpack('C', $packed_data); $ver = $unpacked_data[1]; switch ($ver) { case 0: if ($gameInfo->gameVersion != GameInfo::VERSION_AOKTRIAL) { $gameInfo->gameVersion = GameInfo::VERSION_AOK20; } break; case 1: $gameInfo->gameVersion = GameInfo::VERSION_AOK20A; break; } $pos += 3; } else { switch ($od_type) { case 0x3: if ($gameInfo->gameVersion != GameInfo::VERSION_AOCTRIAL) { $gameInfo->gameVersion = GameInfo::VERSION_AOC10; } break; case 0x4: if ($gameInfo->gameVersion == GameInfo::VERSION_AOC) { $gameInfo->gameVersion = GameInfo::VERSION_AOC10C; } break; } $pos += 20; } } elseif ($command == -1) { // Chat foreach ($this->players as $i => $player) { if ($player->feudalTime != 0 && $player->feudalTime < $time_cnt && $age_flag[$i] < 1) { $this->ingameChat[] = new ChatMessage($player->feudalTime, null, $player->name . ' advanced to Feudal Age'); $age_flag[$i] = 1; } if ($player->castleTime != 0 && $player->castleTime < $time_cnt && $age_flag[$i] < 2) { $this->ingameChat[] = new ChatMessage($player->castleTime, null, $player->name . ' advanced to Castle Age'); $age_flag[$i] = 2; } if ($player->imperialTime != 0 && $player->imperialTime < $time_cnt && $age_flag[$i] < 3) { $this->ingameChat[] = new ChatMessage($player->imperialTime, null, $player->name . ' advanced to Imperial Age'); $age_flag[$i] = 3; } } $packed_data = substr($bodyStream, $pos, 4); $pos += 4; $unpacked_data = unpack('l', $packed_data); $chat_len = $unpacked_data[1]; $chat = substr($bodyStream, $pos, $chat_len); $pos += $chat_len; if ($chat[0] == '@' && $chat[1] == '#' && $chat[2] >= '1' && $chat[2] <= '8') { $chat = rtrim($chat); // throw null-termination character if (substr($chat, 3, 2) == '--' && substr($chat, -2) == '--') { // skip messages like "--Warning: You are being under attack... --" } else { $this->ingameChat[] = ChatMessage::create($time_cnt, $this->players[$chat[2] - 1], substr($chat, 3)); } } } break; // Sync // Sync case 0x2: $packed_data = substr($bodyStream, $pos, 4); $pos += 4; $unpacked_data = unpack('l', $packed_data); $time_cnt += $unpacked_data[1]; // time_cnt is in miliseconds $packed_data = substr($bodyStream, $pos, 4); $pos += 4; $unpacked_data = unpack('l', $packed_data); $unknown = $unpacked_data[1]; if ($unknown == 0) { $pos += 28; } $pos += 12; break; // Command // Command case 0x1: $packed_data = substr($bodyStream, $pos, 4); $pos += 4; $unpacked_data = unpack('l', $packed_data); $length = $unpacked_data[1]; $packed_data = substr($bodyStream, $pos, 1); $pos++; $unpacked_data = unpack('C', $packed_data); $command = $unpacked_data[1]; $pos--; switch ($command) { case 0xb: // player resign $pos++; $packed_data = substr($bodyStream, $pos, 1); $pos++; $unpacked_data = unpack('C', $packed_data); $player_index = $unpacked_data[1]; if (($player = $this->playersByIndex[$player_index]) && $player->resignTime == 0) { $player->resignTime = $time_cnt; $this->ingameChat[] = new ChatMessage($time_cnt, null, $player->name . ' resigned'); } $pos += $length - 2; break; case 0x65: // researches $pos += 8; $packed_data = substr($bodyStream, $pos, 2); $pos += 2; $unpacked_data = unpack('v', $packed_data); $player_id = $unpacked_data[1]; $packed_data = substr($bodyStream, $pos, 2); $pos += 2; $unpacked_data = unpack('v', $packed_data); $research_id = $unpacked_data[1]; if (!($player = $this->playersByIndex[$player_id])) { $pos += $length - 12; break; } switch ($research_id) { case 101: $player->feudalTime = $time_cnt + 130000; break; case 102: $player->castleTime = $player->civId == Civilization::PERSIANS ? $time_cnt + round(160000 / 1.1) : $time_cnt + 160000; break; case 103: $player->imperialTime = $player->civId == Civilization::PERSIANS ? $time_cnt + round(190000 / 1.15) : $time_cnt + 190000; break; } $player->researches[$research_id] = $time_cnt; $pos += $length - 12; break; case 0x77: // training unit $pos += 8; $packed_data = substr($bodyStream, $pos, 2); $pos += 2; $unpacked_data = unpack('v', $packed_data); $unit_type_id = $unpacked_data[1]; $packed_data = substr($bodyStream, $pos, 2); $pos += 2; $unpacked_data = unpack('v', $packed_data); $unit_num = $unpacked_data[1]; if (!isset($this->units[$unit_type_id])) { $this->units[$unit_type_id] = $unit_num; } else { $this->units[$unit_type_id] += $unit_num; } $pos += $length - 12; break; case 0x64: // pc trains unit $pos += 10; $packed_data = substr($bodyStream, $pos, 2); $pos += 2; $unpacked_data = unpack('v', $packed_data); $unit_type_id = $unpacked_data[1]; $unit_num = 1; // always for pc? if (!isset($this->units[$unit_type_id])) { $this->units[$unit_type_id] = $unit_num; } else { $this->units[$unit_type_id] += $unit_num; } $pos += $length - 12; break; case 0x66: // building $pos += 2; $packed_data = substr($bodyStream, $pos, 2); $pos += 2; $unpacked_data = unpack('v', $packed_data); $player_id = $unpacked_data[1]; $pos += 8; $packed_data = substr($bodyStream, $pos, 2); $pos += 2; $unpacked_data = unpack('v', $packed_data); $building_type_id = $unpacked_data[1]; if (in_array($building_type_id, RecAnalystConst::$GATE_UNITS)) { $building_type_id = Unit::GATE; } elseif (in_array($building_type_id, RecAnalystConst::$PALISADE_GATE_UNITS)) { $building_type_id = Unit::PALISADE_GATE; } if (!isset($this->buildings[$player_id][$building_type_id])) { $this->buildings[$player_id][$building_type_id] = 1; } else { $this->buildings[$player_id][$building_type_id]++; } $pos += $length - 14; break; case 0x6c: // tributing $pos++; // player_id_from $packed_data = substr($bodyStream, $pos, 1); $pos++; $unpacked_data = unpack('C', $packed_data); $player_id_from = $unpacked_data[1]; // player_id_to $packed_data = substr($bodyStream, $pos, 1); $pos++; $unpacked_data = unpack('C', $packed_data); $player_id_to = $unpacked_data[1]; // resource_id $packed_data = substr($bodyStream, $pos, 1); $pos++; $unpacked_data = unpack('C', $packed_data); $resource_id = $unpacked_data[1]; // amount_tributed $packed_data = substr($bodyStream, $pos, 4); $pos += 4; $unpacked_data = unpack('f', $packed_data); $amount_tributed = $unpacked_data[1]; // market_fee $packed_data = substr($bodyStream, $pos, 4); $pos += 4; $unpacked_data = unpack('f', $packed_data); $market_fee = $unpacked_data[1]; $playerFrom = $this->playersByIndex[$player_id_from]; $playerTo = $this->playersByIndex[$player_id_to]; if ($playerFrom && $playerTo) { $tribute = new Tribute(); $tribute->time = $time_cnt; $tribute->playerFrom = $playerFrom; $tribute->playerTo = $playerTo; $tribute->resourceId = $resource_id; $tribute->amount = floor($amount_tributed); $tribute->fee = $market_fee; $this->tributes[] = $tribute; } $pos += $length - 12; break; default: $pos += $length; break; } $pos += 4; break; default: /* detect if this is a header of saved chapter */ /* sometimes header of the saved chapter is in $03 command, instead of $20 as it should be, when this happens the length of $20 command is $0E, otherwise it is $02 (always?, rule?), we do not rely on it, that's why we are skipping saved chapter data here and not in $20 command */ if ($pos == $this->_nextPos - $this->_headerLen - 4) { /* this is a header of saved chapter data, we have already read next_command_block that's why -4 in the if-statement */ /* next_pos - header_len = offset of compressed chapter data */ $next_command_block = $od_type; $packed_data = substr($bodyStream, $pos, 4); $pos += 4; $unpacked_data = unpack('l', $packed_data); $this->_nextPos = $unpacked_data[1]; // next_chapter_pos $pos = $next_command_block - $this->_headerLen - 8; } else { // shouldn't occure, just to prevent unexpected endless cycling $pos++; } break; } } unset($bodyStream); $gameInfo->playTime = $time_cnt; return true; }