Как декодировать определенную строку байтов переменных в PHP - PullRequest
0 голосов
/ 10 июня 2019

Я пытаюсь декодировать строку в определенном формате (код колоды Hearthstone), используя PHP, например:

AAEBAc2xAgjAAe0E7QX3DdYRh6wC8fsCoIADC8kDqwTLBPsMhRDH0wKW6AK0/ALNiQPXiQOfmwMA

или

AAEBAf0GBAD6DoyAA6CAAw37AZwCigbJB/gHlA+CEIUQrRDy0AL2/QKJgAPRgAMA

Спецификация ( исходное описание ):

Строка данных представляет собой base64 кодированную строку байтов.

в противном случае каждое последующее значение является целым числом, закодированным как unsigned varint.

  1. Блок заголовка

    • Зарезервированный байт 0x00
    • Версия (1)
    • Формат
  2. Блок данных
    Блок данных разбит на четыре пары длины + массив вв следующем порядке:

    • Герои
    • Карты с одним экземпляром
    • Карты с 2 копиями
    • Карты с n-копиями

Каждая пара имеет начальный varint, определяющий количество элементов в массиве.Для первых трех блоков это массивы varints.Для последнего блока это массив пар varints.Цель этой структуры - сделать строку данных максимально компактной.

Я начал что-то собирать, но я новичок, когда дело доходит до обработки необработанных байтов.Мой код:

    // I found this to decode Variable-length quantity (varint)
    function vlq_decode(array $bytes) {
        $result = [];
        $integer = 0;
        foreach ($bytes as $byte) {
            if ($integer > 0xfffffff - 0x7f) {
                throw new OverflowException('The value exceeds the maximum allowed.');
            }
            $integer <<= 7;
            $integer |= 0x7f & $byte;

            if (($byte & 0x80) === 0) {
                $result[] = $integer;
                $integer = 0;
            }
        }
        if (($byte & 0x80) !== 0) {
            throw new InvalidArgumentException('Incomplete byte sequence.');
        }
        return $result;
    }

    $datastring = 'AAEBAc2xAgjAAe0E7QX3DdYRh6wC8fsCoIADC8kDqwTLBPsMhRDH0wKW6AK0/ALNiQPXiQOfmwMA';

    $raw = base64_decode($datastring);

    $byte_array = unpack('C*', $raw);

    $result = vlq_decode($byte_array);

    print_r($result);

Единственное, в чем я уверен, это base64_decode.Я не могу сказать, правильные ли параметры unpack, или работает ли функция vlq_decode, как предполагалось, потому что я сам ее не написал.

На оригинальном сайте есть эталонные реализации в Python и Javascript, но они у меня над головой, и я не могу использовать код для своей работы.

Обновление:

Код действительно выдает array, который выглядит примерно так, как я ожидал, но многие значения не кажутся правильными.Я думаю, что преобразование из varint все еще несколько отклонено.

// this is the $result I get (wrong)
Array (
    [0] => 0 // this is always 0
    [1] => 1 // Version
    [2] => 1 // Format
    [3] => 1 // What follows is an array of length 1 (data block Heroes)
    [4] => 1267842
    [5] => 8 // What follows is an array of length 8 (data block single-copy cards)
    [6] => 8193
    [7] => 13956
    [8] => 13957
    [9] => 15245
    [10] => 11025
    [11] => 120322
    [12] => 1867138
    [13] => 524291
    [14] => 11 // What follows is an array of length 11 (data block 2-copy cards)
    [15] => 9347
    [16] => 5508
    [17] => 9604
    [18] => 15756
    [19] => 656
    [20] => 1173890
    [21] => 373762
    [22] => 867842
    [23] => 1262723
    [24] => 1426563
    [25] => 511363
    [26] => 0  // What follows is an array of length 0 (data block n-copy cards)
)

Реализация Python ( Gist ) выдает разные числа в несколько ином формате, которые хорошо соответствуют база данных , содержащая данные для идентификаторов (в поле dbfId)

// this is the expected (correct) $result
Array (
    [0] => 0
    [1] => 1
    [2] => 1
    [3] => 1
    [4] => 39117
    [5] => 8
    [6] => 192 
    [7] => 621 
    [8] => 749 
    [9] => 1783 
    [10] => 2262 
    [11] => 38407 
    [12] => 48625 
    [13] => 49184 
    [14] => 11
    [15] => 457 
    [16] => 555 
    [17] => 587 
    [18] => 1659 
    [19] => 2053 
    [20] => 43463 
    [21] => 46102 
    [22] => 48692 
    [23] => 50381 
    [24] => 50391 
    [25] => 52639
    [26] => 0
)

Любая помощь приветствуется!

Там уже есть вопрос по этой теме , но он был плохо написан без примеров кода, поэтому я делаю еще один шаг.

1 Ответ

1 голос
/ 12 июня 2019

Это проблема endian , иначе вам нужно выдвинуть биты из каждого байта varint в обратном порядке. Подсказка в том, что значения ниже 128 делают его беспрепятственным.

Ниже приведен пример взлома, который не должен использоваться в реальном коде:

str_split(decbin(1267842),7)

Урожайность:

array(3) {
  [0]=>
  string(7) "1001101"
  [1]=>
  string(7) "0110001"
  [2]=>
  string(7) "0000010"
}

Очень удобно, что оно уже кратно 7 битам, но, вероятно, также является признаком проблемы с порядком байтов.

Обратное, взорвать, преобразовать обратно:

bindec(implode('', array_reverse(str_split(decbin(1267842),7))))

Урожайность:

int(39117)

Я перенастроил эту функцию, чтобы иметь возможность решить эту проблему:

function vlq_decode(array $bytes, $swap_endian=false) {
    $result = [];
    $segments = [];
    foreach ($bytes as $byte) {
        if( $swap_endian ) {
            array_unshift($segments, 0x7f & $byte);
        } else {
            $segments[] = ( 0x7f & $byte );
        }

        if (($byte & 0x80) === 0) {
            $integer = 0;
            foreach($segments as $segment) {
                $integer <<= 7;
                $integer |= ( 0x7f & $segment );
            }
            $result[] = $integer;
            $segments = [];
        }
    }
    if (($byte & 0x80) !== 0) {
        throw new InvalidArgumentException('Incomplete byte sequence.');
    }
    return $result;
}

и тогда vlq_decode($byte_array, true); даст вам то, что вы хотите.

Я сократил этот код переполнения койки, так как он никогда не обнаружит фактический код, а также затруднил вам 32-битные числа. Если вы do хотите обнаружить переполнение во время декодирования, вам нужно сосчитать биты, которые вы распаковываете, и это просто боль в заднице: P

...