@etothepii/satisfactory-file-parser
Version:
A file parser for satisfactory files. Includes save files and blueprint files.
242 lines (241 loc) • 9.8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ByteStreamReader = void 0;
const alignment_enum_1 = require("../byte/alignment.enum");
const byte_writer_class_1 = require("../byte/byte-writer.class");
const parser_error_1 = require("../error/parser.error");
class ByteStreamReader {
constructor(reader, onCloseCallback, timeout = 30000, maxBufferThreshold = 100 * 1000 * 1000, alignment = alignment_enum_1.Alignment.LITTLE_ENDIAN) {
this.reader = reader;
this.onCloseCallback = onCloseCallback;
this.timeout = timeout;
this.maxBufferThreshold = maxBufferThreshold;
this.alignment = alignment;
this.debug = false;
this.closedAlready = false;
this.totalNumberOfBytesRead = 0;
this.trailingBufferSize = 0;
this.continuousRead = async () => {
while (!this.inputStreamIsDone && !this.paused) {
const { done, value } = await this.reader.read();
if (!this.inputStreamIsDone) {
if (value) {
const trailingPos = Math.max(0, this.currentByte - this.trailingBufferSize);
this.operatingStreamBuffer = new Uint8Array(byte_writer_class_1.ByteWriter.AppendBuffer(this.operatingStreamBuffer.slice(trailingPos), value));
this.operatingDataView = new DataView(this.operatingStreamBuffer.buffer);
this.currentByte = Math.min(this.currentByte, this.trailingBufferSize);
if (this.operatingStreamBuffer.byteLength > this.maxBufferThreshold) {
console.warn('pausing due to size.');
this.pause();
}
if (this.callbackAfterRead) {
this.callbackAfterRead();
}
}
}
else {
this.close();
}
this.inputStreamIsDone = done;
}
};
this.operatingStreamBuffer = new Uint8Array();
this.operatingDataView = new DataView(this.operatingStreamBuffer.buffer);
this.inputStreamIsDone = false;
this.currentByte = 0;
this.neededNextAmountOfBytes = 0;
this.paused = false;
this.trailingBufferSize = 2000;
this.readLoopHandle = setTimeout(this.continuousRead);
this.waitingTimeoutHandle = undefined;
}
pause() {
this.paused = true;
clearTimeout(this.readLoopHandle);
this.readLoopHandle = undefined;
if (this.waitingTimeoutHandle) {
clearTimeout(this.waitingTimeoutHandle);
this.waitingTimeoutHandle = undefined;
}
}
resume() {
if (this.paused) {
this.paused = false;
this.readLoopHandle = setTimeout(this.continuousRead);
}
}
hasInput() {
return !this.inputStreamIsDone;
}
async close() {
this.inputStreamIsDone = true;
this.pause();
await this.onCloseCallback();
this.closedAlready = true;
}
async allocate(count) {
if (count > this.maxBufferThreshold * 0.5) {
console.warn(`you are waiting for a very big chunk here mate. ${count} bytes. U sure you have your numbers right?`);
}
if (this.getAmountAllocatedLeft() < count) {
if (this.inputStreamIsDone) {
throw new parser_error_1.CorruptSaveError('Input Stream has finished before needed data was received.');
}
if (this.paused) {
this.resume();
}
await this.waitUntilNumBytesAvailable(count);
}
}
waitUntilNumBytesAvailable(count) {
this.neededNextAmountOfBytes = count;
return new Promise((resolve, reject) => {
if (this.getAmountAllocatedLeft() >= this.neededNextAmountOfBytes) {
this.neededNextAmountOfBytes = 0;
return resolve();
}
this.callbackAfterRead = () => {
if (this.getAmountAllocatedLeft() >= this.neededNextAmountOfBytes) {
if (this.waitingTimeoutHandle) {
clearTimeout(this.waitingTimeoutHandle);
}
this.neededNextAmountOfBytes = 0;
this.callbackAfterRead = undefined;
return resolve();
}
};
if (this.waitingTimeoutHandle) {
clearTimeout(this.waitingTimeoutHandle);
}
this.waitingTimeoutHandle = setTimeout(() => {
if (!this.inputStreamIsDone) {
reject(new parser_error_1.TimeoutError(`Timed out before ${this.neededNextAmountOfBytes} bytes were available to read next.`));
}
return resolve();
}, this.timeout);
});
}
skipBytes(byteLength = 1) {
this.currentByte += byteLength;
this.totalNumberOfBytesRead += byteLength;
return;
}
readByte() {
this.totalNumberOfBytesRead += 1;
return this.operatingDataView.getUint8(this.currentByte++);
}
readBytes(count) {
this.totalNumberOfBytesRead += count;
return new Uint8Array(new Array(count).fill(0).map(pl => this.operatingDataView.getUint8(this.currentByte++)));
}
readHex(hexLength) {
let hexPart = [];
for (let i = 0; i < hexLength; i++) {
let currentHex = String.fromCharCode(this.operatingDataView.getUint8(this.currentByte++));
hexPart.push(currentHex);
}
this.totalNumberOfBytesRead += hexLength;
return hexPart.join('');
}
readInt8() {
this.totalNumberOfBytesRead += 1;
let data = this.operatingDataView.getInt8(this.currentByte++);
return data;
}
readUint8() {
this.totalNumberOfBytesRead += 1;
let data = this.operatingDataView.getUint8(this.currentByte++);
return data;
}
readInt16() {
this.totalNumberOfBytesRead += 2;
let data = this.operatingDataView.getInt16(this.currentByte, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
this.currentByte += 2;
return data;
}
readUint16() {
this.totalNumberOfBytesRead += 2;
let data = this.operatingDataView.getUint16(this.currentByte, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
this.currentByte += 2;
return data;
}
readInt32() {
this.totalNumberOfBytesRead += 4;
let data = this.operatingDataView.getInt32(this.currentByte, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
this.currentByte += 4;
return data;
}
readUint32() {
this.totalNumberOfBytesRead += 4;
let data = this.operatingDataView.getUint32(this.currentByte, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
this.currentByte += 4;
return data;
}
readLong() {
this.totalNumberOfBytesRead += 8;
let data = this.operatingDataView.getBigInt64(this.currentByte, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
this.currentByte += 8;
return data;
}
readInt64() {
return this.readLong();
}
readUint64() {
this.totalNumberOfBytesRead += 8;
let data = this.operatingDataView.getBigUint64(this.currentByte, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
this.currentByte += 8;
return data;
}
readFloat32() {
this.totalNumberOfBytesRead += 4;
let data = this.operatingDataView.getFloat32(this.currentByte, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
this.currentByte += 4;
return data;
}
readDouble() {
this.totalNumberOfBytesRead += 8;
let data = this.operatingDataView.getFloat64(this.currentByte, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
this.currentByte += 8;
return data;
}
readString() {
let strLength = this.readInt32();
if (strLength === 0) {
return '';
}
if (strLength > (this.operatingDataView.buffer.byteLength - this.currentByte)) {
let errorMessage = `Cannot read string of length ${strLength} at position ${this.currentByte} as it exceeds the end at ${this.operatingDataView.buffer.byteLength}`;
throw new Error(errorMessage);
}
if (strLength < 0) {
const string = new Array(-strLength - 1).fill('').map(c => String.fromCharCode(this.readUint16()));
this.currentByte += 2;
this.totalNumberOfBytesRead += 2;
return string.join('');
}
try {
const string = new Array(strLength - 1).fill('').map(c => String.fromCharCode(this.readUint8()));
this.currentByte += 1;
this.totalNumberOfBytesRead += 1;
return string.join('');
}
catch (error) {
let errorMessage = `Cannot read UTF8 string of length ${strLength} at position ${this.currentByte}.`;
console.log(errorMessage, error);
throw error;
}
}
getBufferPosition() {
return this.totalNumberOfBytesRead;
}
getBufferProgress() {
throw new parser_error_1.UnimplementedError();
}
getBufferLength() {
return this.operatingStreamBuffer.byteLength;
}
getAmountAllocatedLeft() {
return this.operatingStreamBuffer.byteLength - this.currentByte;
}
}
exports.ByteStreamReader = ByteStreamReader;