Search code examples
phparraysbytesteam

Decode a CS:GO match sharing code with PHP


I am trying to build a function which decodes a CS:GO match sharing code. I have seen enough examples but everyhting is in JS or C# but nothing in PHP.

I took the akiver demo manager as an example and i tried to replicate it in PHP. I am going bit blind because i have no idea what is the output on a certain points so i can only hope that the result will be what i expect it to be. I think i am on the right path, the problem comes when the bytes have to be created/interpeted/converted to the desire outcome.

The code that should be decoded is the following: 'CSGO-oPRbA-uTQuR-UFkiC-hYWMB-syBcO' ($getNextGame variable)

The result should be 3418217537907720662

My code so far:

 /**
 * @param $getNextGame
 * @return array
 */
 public function decodeDemoCode(string $getNextGame): array
 {
     $shareCodePattern = "/CSGO(-?[\w]{5}){5}$/";
     if (preg_match($shareCodePattern, $getNextGame) === 1) {
        $result = [];
        $bigNumber = 0;
        $matchIdBytes = $outcomeIdBytes = $tvPortIdBytes = [];
        $dictionary = "ABCDEFGHJKLMNOPQRSTUVWXYZabcdefhijkmnopqrstuvwxyz23456789";
        $dictionaryLength = strlen($dictionary);

        $changedNextGame = str_replace(array("CSGO", "-"), "", $getNextGame);
        $chars = array_reverse(str_split($changedNextGame));
        foreach ($chars as $char) {
           $bigNumber = ($bigNumber * $dictionaryLength) + strpos($dictionary, $char);
        }
     }
 }

This brings me back something like that:

1.86423701402E+43 (double)

Then i have the following:

$packed = unpack("C*", $bigNumber);
$reversedPacked = array_reverse($packed);

and this brings the following back:

array(17 items)
   0 => 51 (integer)
   1 => 52 (integer)
   2 => 43 (integer)
   3 => 69 (integer)
   4 => 50 (integer)
   5 => 48 (integer)
   6 => 52 (integer)
   7 => 49 (integer)
   8 => 48 (integer)
   9 => 55 (integer)
   10 => 51 (integer)
   11 => 50 (integer)
   12 => 52 (integer)
   13 => 54 (integer)
   14 => 56 (integer)
   15 => 46 (integer)
   16 => 49 (integer)

Now here i am not really sure what to do because i do not completely understand C# and i have never worked with bytes in PHP before.

Generally the return type should be an array and would look something like that:

$result = [
   matchId => 3418217537907720662,
   reservationId => 3418217537907720662,
   tvPort => 55788
];

Thanks in advance. Any help is deeply appreciated


Solution

  • I have created a PHP class which makes that possible:

    CS:GO ShareCode Decoder PHP

    The first problem you have to solve is the returned double value. PHP has limitation when it comes to big integers. More to that here What is the maximum value for an integer in PHP.

    Because of this limitation you are losing precision leading to inaccurate results. In order to solve this problem you will have to use one of these libraries GMB, BC Math. What these libraries do, is to give you back the result as a string which solves the double value you got.

    So your code has to look something like that:

    foreach ($chars as $char) {
          $bigNumber = gmp_add(
             gmp_mul($bigNumber,$dictionaryLength),
             strpos($dictionary,$char)
         );
    }
    json_encode($bigNumber);
    $result = json_decode($bigNumber, true, 512, JSON_BIGINT_AS_STRING);
    

    This will give you back the following "18642370140230194654275126136176397505221000"

    You do not really need the PHP pack and unpack functions since the results can be generated without them. The next step is to convert your number to hexadecimal. You can do that with the following:

    $toHex = gmp_strval(gmp_init($number, 10), 16);
    

    Again you need to use the gmp library in order to get the desired value. What you do, is to make sure that the result is a string and then you convert your number's base from 10 to 16 which is the equivalent of hexadecimal. The results is the following:

    "d6010080bdf26f2fbf0100007cf76f2f5188"
    

    The next step is to convert the hex value to an array of byte integers. It looks like this:

    $bytes = [];
    $byteArray= str_split($toHex, 2);
    foreach ($byteArray as $byte) {
        $bytes[] = (int)base_convert($byte, 16, 10);
    }
    

    What you do here is to split the array to every two chars. The $byteArray variable looks like this (before it enters the foreach loop)

    array(18 items)
       0 => 'd6' (2 chars) 1 => '01' (2 chars) 2 => '00' (2 chars) 3 => '80' (2 chars)
       4 => 'bd' (2 chars) 5 => 'f2' (2 chars) 6 => '6f' (2 chars) 7 => '2f' (2 chars)
       8 => 'bf' (2 chars) 9 => '01' (2 chars) 10 => '00' (2 chars) 11 => '00' (2 chars)
       12 => '7c' (2 chars) 13 => 'f7' (2 chars) 14 => '6f' (2 chars) 15 => '2f' (2 chars)
       16 => '51' (2 chars) 17 => '88' (2 chars)
    

    Now you will have to convert each entry into integer. Since the results are not that big anymore you can change the base of your values with the base_convert function. The base is 16 (hex) and you will have to change it back to 10. The results $bytes after the foreach loop looks like this:

    array(18 items)
       0 => 214 (integer) 1 => 1 (integer) 2 => 0 (integer) 3 => 128 (integer)
       4 => 189 (integer) 5 => 242 (integer) 6 => 111 (integer) 7 => 47 (integer)
       8 => 191 (integer) 9 => 1 (integer) 10 => 0 (integer) 11 => 0 (integer)
       12 => 124 (integer) 13 => 247 (integer) 14 => 111 (integer) 15 => 47 (integer)
       16 => 81 (integer) 17 => 136 (integer)
    

    Now you have to define which bytes are responsible for each result.

    $matchIdBytes = array_reverse(array_slice($bytes, 0, 8));
    $reservationIdBytes = array_reverse(array_slice($bytes, 8, 8));
    $portBytes = array_reverse(array_slice($bytes, 16, 2));
    
    • For the match id you will have to get the first 8 entries and the reverse the array
    • For the reservation id you will have to get the next 8 entries starting from the 8th entry and reverse the array
    • For the port you will have to get the last 2 entries and reverse the array

    Now you will have to return the value

      return [
           'matchId' =>  $this->getResultFromBytes($matchIdBytes),
           'reservationId' => $this->getResultFromBytes($reservationIdBytes),
           'tvPort' =>  $this->getResultFromBytes($portBytes)
      ];
    

    The getResultFromBytes() function:

     **
     * @param array $bytes
     * @return string
     */
     public function getResultFromBytes(array $bytes): string
     {
        $chars = array_map("chr", $bytes);
        $bin = implode($chars);
        $hex = bin2hex($bin);
        return gmp_strval($this->gmp_hexDec($hex));
      }
    
     /**
     * @param $n
     * @return string
     */
    public function gmp_hexDec($n): string
    {
        $gmp = gmp_init(0);
        $multi = gmp_init(1);
        for ($i=strlen($n)-1;$i>=0;$i--,$multi=gmp_mul($multi, 16)) {
            $gmp = gmp_add($gmp, gmp_mul($multi, hexdec($n[$i])));
        }
        return $gmp;
    }
    

    Best regards