/**
  * 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;
 }