В настоящее время я пытаюсь портировать 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);
}
Я ценю любой вид душа, которая может иметь некоторое время, чтобы указать мне правильное направление!
Если потребуется дополнительная информация, я с удовольствием предоставлю ее.