Ejemplo n.º 1
0
 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"];
 }
Ejemplo n.º 2
0
 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;
 }
Ejemplo n.º 3
0
 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;
 }