Портирование старого двоичного парсера воспроизведения в Rocket League из Javascript на проблемы Typescript - PullRequest
0 голосов
/ 21 февраля 2020

В настоящее время я пытаюсь портировать pvormste Rocket League (не поддерживаемый) анализатор воспроизведения в Typescript, и столкнулся с множеством проблем на этом пути. Он использует замечательный двоичный парсер keichi .

Самая большая проблема в том, что я не совсем хорошо понимаю двоичный файл, и что логика парсера c и предположения относительно данных может быть очень устаревшим (двоичные буферы файла воспроизведения могли полностью измениться) после того, как его не поддерживали годами.

В любом случае, моя текущая идея - создать веб-сайт, где пользователи перетаскивают свои файлы воспроизведения на мой клиент, который читает эти файлы с помощью readReArArBBuffer из FileReader, а затем анализатор выводит JSON со всеми соответствующими данными.

Перетащите и пр. c. работают нормально, и вот где я сейчас нахожусь с парсером:

import { Parser } from 'binary-parser';

const selectDataType = function() {
  if (this.type === 'IntProperty') {
    return 1;
  } else if (this.type === 'ArrayProperty') {
    return 2;
  } else if (this.type === 'StrProperty' || this.type === 'NameProperty') {
    return 3;
  } else if (this.type === 'ByteProperty') {
    return 4;
  } else if (this.type === 'QWordProperty') {
    return 5;
  } else if (this.type === 'BoolProperty') {
    return 6;
  } else if (this.type === 'FloatProperty') {
    return 7;
  }
};

// A function which returns 1 if the property name is "None", 0 if not.
// Again the parser choices needs int keys.
const readUntilNone = function(item, buf) {
  return item.name === 'None' || item.name === 'none' || !item.name;
};

const isNone = function() {
  if (this.name === 'None') {
    return 1;
  }

  return 0;
};

// JS can not handle 64 bit integers, so the parser reads 2 32 bit integers.
// This function converts the 32 bit integer from decimal to hex (little endian).
const decToHex = function(item) {
  let hex = parseInt(item, 10).toString(16);
  let len = 8 - hex.length;

  if (len > 0) {
    for (len; len > 0; --len) {
      hex = '0' + hex;
    }
  }

  return hex;
};

// ------------------------------------------------------------------------------
// Value parsers
// ------------------------------------------------------------------------------

// Parses an 32 bit integer.
// tslint:disable: member-ordering
const IntProperty = new Parser().endianess('little').int32('value');

// Parses a 32 bit integer length value of the following string
// and the string itself with Null stripped.
const StrProperty = new Parser()
  .endianess('little')
  .int32('vl')
  .string('value', { encoding: 'ascii', length: 'vl', stripNull: true });

// The byte property contains 2 strings prefixed by 2 32 bit integers as lengths
// (Strange byte type ...)
const ByteProperty = new Parser()
  .endianess('little')
  .int32('vl1')
  .string('value1', { encoding: 'ascii', length: 'vl1', stripNull: true })
  .int32('vl2')
  .string('value2', { encoding: 'ascii', length: 'vl2', stripNull: true });

// Parses a 64 bit integer into 2 32 bit integers as hex values.
const QWordProperty = new Parser()
  .endianess('little')
  .int32('hex2', { formatter: decToHex })
  .int32('hex1', { formatter: decToHex });

// Parses 0 or 1 (1 byte).
const BoolProperty = new Parser().endianess('little').bit8('value');

// Parses a 32 bit single precision value.
const FloatProperty = new Parser().floatle('value');

// ------------------------------------------------------------------------------
// Class index parser
// ------------------------------------------------------------------------------
const ClassIndex = new Parser()
  .endianess('little')
  .int32('cl')
  .string('class', { encoding: 'ascii', length: 'cl', stripNull: true })
  .int32('index');

// ------------------------------------------------------------------------------
// Array Parsers
// ------------------------------------------------------------------------------

// This parser is like the property parser, just for the properties of the array.
// See below for description.
const ArrayPropertyDetail = new Parser()
  .endianess('little')
  .int32('nl')
  .string('name', { encoding: 'ascii', length: 'nl', stripNull: true })
  .choice('more', {
    tag: isNone,
    choices: {
      1: new Parser(),
      0: new Parser()
        .namely('self')
        .endianess('little')
        .int32('tl')
        .string('type', { encoding: 'ascii', length: 'tl', stripNull: true })
        .int32('unkn1')
        .int32('unkn2')
        .choice('details', {
          tag: selectDataType,
          choices: {
            1: IntProperty,
            3: StrProperty,
            4: ByteProperty,
            5: QWordProperty,
            6: BoolProperty,
            7: FloatProperty,
          },
        }),
    },
  });

// This parser parses the array property, which has a length of elements
// (like 4 representing 4 Goals) and a array of goal properties.
const ArrayProperty = new Parser()
  .endianess('little')
  .int32('length')
  .array('array', {
    type: new Parser().array('part', { type: ArrayPropertyDetail, readUntil: readUntilNone }),
    length: 'length',
  });

// ------------------------------------------------------------------------------
// Tickmark parser
// ------------------------------------------------------------------------------
const TickMark = new Parser()
  .endianess('little')
  .int32('tl')
  .string('type', { encoding: 'ascii', length: 'tl', stripNull: true })
  .int32('frame');

// ------------------------------------------------------------------------------
// Debug Log parser
// ------------------------------------------------------------------------------
const DebugLog = new Parser()
  .endianess('little')
  .int32('frame_number')
  .int32('pl')
  .string('player', { encoding: 'ascii', length: 'pl', stripNull: true })
  .int32('tl')
  .string('text', { encoding: 'ascii', length: 'tl', stripNull: true });

// ------------------------------------------------------------------------------
// Keyframe parser
// ------------------------------------------------------------------------------
const Keyframe = new Parser()
  .floatle('time')
  .int32('frame')
  .int32('file_position');

// ------------------------------------------------------------------------------
// Property parser
// ------------------------------------------------------------------------------

// This Parser parses the property section. It contains every single match
// information like goals, teamsize, score, etc.
// Every property has a name (string) prefixed by a length (int32).
// Then a type information (string) prefixed by a length (int32).
// And the value of the property (Selecting the right value parser).
// Btw: After the property type there are 2 unknown 32 bit integers.
const Property = new Parser()
  .endianess('little')
  .int32('nl')
  .string('name', { zeroTerminated: true })
  .choice('more', {
    tag: isNone,
    choices: {
      1: new Parser(),
      0: new Parser()
        .endianess('little')
        .int32('tl')
        .string('type', { zeroTerminated: true })
        .int32('unkn1')
        .int32('unkn2')
        .choice('details', {
          tag: selectDataType,
          choices: {
            1: IntProperty,
            2: ArrayProperty,
            3: StrProperty,
            4: ByteProperty,
            5: QWordProperty,
            6: BoolProperty,
            7: FloatProperty,
          },
        }),
    },
  });

export class RLParser {
  // ==============================================================================
  // Helper functions
  // ==============================================================================

  RocketLeagueParser = new Parser()
    .endianess('little')
    .int32('identifier')
    .int32('crc')
    .int32('version_major')
    .int32('version_minor')
    .int32('tl')
    .string('type', {
      zeroTerminated: true,
    })
    .array('properties', {
      type: Property,
      readUntil: readUntilNone,
    })
    .int32('length_of_remaining_data')
    .uint32('unkn1')
    .int32('ldl') // Leveldata length
    .array('level_data', {
      type: StrProperty,
      length: 'ldl',
    })
    .int32('kl') // Keyframes length
    .array('keyframes', {
      type: Keyframe,
      length: 'kl',
    })
    .int32('dll') // Debugging log length
    .array('debugging_log', {
      type: DebugLog,
      length: 'dll',
    })
    .int32('tml') // Tickmarks length
    .array('tickmarks', {
      type: TickMark,
      length: 'tml',
    })
    .int32('pl') // Packages length
    .array('packages', {
      type: StrProperty,
      length: 'pl',
    })
    .int32('ol') // Objects length
    .array('objects', {
      type: StrProperty,
      length: 'ol',
    })
    .int32('nl') // Names length
    .array('names', {
      type: StrProperty,
      length: 'nl',
    })
    .int32('cil') // Class index length
    .array('class_indices', {
      type: ClassIndex,
      length: 'cil',
    });
}

Я застрял на Ошибка: встречается неопределенное значение тега undefined при выборе , что относится к selectDataType функция, которая ожидает тип (т.е. IntProperty), но вместо этого получает «TeamSize». Поэтому я предполагаю, что логика c до сих пор верна, но в цепочке синтаксического анализатора отсутствует что-то промежуточное, или выбор неправильный, и у меня нет ноу-хау, чтобы определить, что это такое.

На всякий случай вот файл воспроизведения, который я пытаюсь проанализировать: https://drive.google.com/open?id=1_BaAlLP8v0ox1PUh6jEjiz9Wc05CcSCG

и вот функция синтаксического анализа:

replayParser(files: any): void {
    const reader = new FileReader();
    const replayParser = new RLParser();
    reader.onload = e => {
      const event: any = e.target;
      const buffer = Buffer.from(event.result);
      const result = replayParser.RocketLeagueParser.parse(buffer);
      console.log(result);
    };
    reader.readAsArrayBuffer(files[0].file);
  }

Я ценю любой вид душа, которая может иметь некоторое время, чтобы указать мне правильное направление!

Если потребуется дополнительная информация, я с удовольствием предоставлю ее.

...