@etothepii/satisfactory-file-parser
Version:
A file parser for satisfactory files. Includes save files and blueprint files.
226 lines (225 loc) • 9.8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.StreamParserReader = void 0;
const alignment_enum_1 = require("../byte/alignment.enum");
class StreamParserReader {
constructor(minBufferSize) {
this.minBufferSize = minBufferSize;
this.alignment = alignment_enum_1.Alignment.LITTLE_ENDIAN;
this.getAmountLeftToRead = () => {
return this.view.byteLength - this.currentByte;
};
this.parseLogic = async () => {
console.log('before allocate 1', this.getAmountLeftToRead(), this.inputBuffer.length);
await this.allocate(100000);
const unknownStuff = [
this.readInt32(),
this.readInt32(),
this.readString(),
this.readInt32(),
this.readInt32(),
this.readInt32(),
this.readString()
];
console.log('before allocate 2', unknownStuff, this.getAmountLeftToRead(), this.inputBuffer.length);
await this.allocate(10000);
};
this.allocate = async (amount) => {
if (amount > this.minBufferSize) {
throw new Error(`Can not attempt to allocate (${amount}) more than the size of the buffer (${this.minBufferSize}) is.`);
}
if (this.getAmountLeftToRead() >= amount) {
return Promise.resolve();
}
return new Promise((resolve, reject) => {
console.log('Waiting for allocate', amount, this.getAmountLeftToRead());
this.readTilBufferFullOrInputEmpty();
console.log('shift?', this.inputBuffer.length);
if (this.inputBuffer.length > 0 && this.inputBuffer.length + this.getAmountLeftToRead() >= amount) {
this.shiftBufferIntoOperatingDataView();
return resolve();
}
this.onInputDataAvailableCallback = () => {
console.log('on available callback.', this.inputBuffer.length, this.getAmountLeftToRead(), amount);
if (this.inputBuffer.length > 0 && this.inputBuffer.length + this.getAmountLeftToRead() >= amount) {
this.shiftBufferIntoOperatingDataView();
console.log(this.getAmountLeftToRead());
this.onInputDataAvailableCallback = undefined;
return resolve();
}
};
});
};
this.waitForAmountLeftToRead = async (amount, callback = () => { }) => {
while (this.getAmountLeftToRead() < amount) {
if (this.hasInputStreamEnded) {
throw new Error(`Unexpected end of stream. Waited to read ${amount} but stream ended prematurely.`);
}
console.log('###waiting... for ', amount);
this.readTilBufferFullOrInputEmpty();
if (this.inputBuffer.length > 0) {
console.log('###shift.');
this.shiftBufferIntoOperatingDataView();
}
await this.wait(20);
}
console.log('amount left to read is no longer smaller than', amount, this.getAmountLeftToRead());
callback();
};
this.wait = (ms) => new Promise((resolve, reject) => {
setTimeout(() => {
return resolve(ms);
}, ms);
});
this.shiftBufferIntoOperatingDataView = (amountOfTrailingBytesToKeep = 300) => {
const resultingTrailingBytes = Math.min(this.currentByte, amountOfTrailingBytesToKeep);
const trailingPlusLeftToRead = new Uint8Array(this.buffer.slice(this.currentByte - resultingTrailingBytes));
const { length, ...chunks } = this.inputBuffer;
const inBuffer = new Uint8Array(Buffer.concat(Object.values(chunks)));
this.inputBuffer = { length: 0 };
this.buffer = new Uint8Array(trailingPlusLeftToRead.byteLength + inBuffer.byteLength);
this.buffer.set(trailingPlusLeftToRead, 0);
this.buffer.set(inBuffer, trailingPlusLeftToRead.byteLength);
this.currentByte = resultingTrailingBytes;
this.view = new DataView(this.buffer.buffer);
};
this.readTilBufferFullOrInputEmpty = () => {
let chunk;
console.log(this.hasDataToRead, this.inputBuffer.length, this.minBufferSize);
while (this.hasDataToRead && this.inputBuffer.length < this.minBufferSize && (chunk = this.input.read()) != null) {
console.log('reading chunk', chunk);
this.inputBuffer[chunk.byteLength] = chunk;
this.inputBuffer.length += chunk.length;
}
if (chunk === null) {
console.log('chunk is null, input empty for now.');
this.hasDataToRead = false;
}
else if (this.inputBuffer.length >= this.minBufferSize) {
console.log('buffer is full, ignoring for now.', this.inputBuffer.length);
}
console.log('data available callback?', this.onInputDataAvailableCallback);
if (this.onInputDataAvailableCallback) {
this.onInputDataAvailableCallback();
}
};
this.buffer = new Uint8Array(0);
this.view = new DataView(this.buffer.buffer);
this.currentByte = 0;
this.hasInputStreamEnded = false;
this.inputBuffer = { length: 0 };
this.hasDataToRead = false;
}
startReading(input) {
this.input = input;
this.input.pause();
this.input.on('readable', () => {
console.log('on readable. Reading til buffer full or input empty.');
this.hasDataToRead = true;
this.readTilBufferFullOrInputEmpty();
});
this.input.on('end', () => {
this.hasDataToRead = false;
this.hasInputStreamEnded = true;
});
this.input.on('close', () => {
this.hasDataToRead = false;
this.hasInputStreamEnded = true;
});
}
skipBytes(byteLength = 1) {
this.currentByte += byteLength;
return;
}
readByte() {
return parseInt(this.view.getUint8(this.currentByte++).toString());
}
readBytes(count) {
return new Uint8Array(new Array(count).fill(0).map(pl => this.view.getUint8(this.currentByte++)));
}
readHex(hexLength) {
let hexPart = [];
for (let i = 0; i < hexLength; i++) {
let currentHex = String.fromCharCode(this.view.getUint8(this.currentByte++));
hexPart.push(currentHex);
}
return hexPart.join('');
}
readInt8() {
let data = this.view.getInt8(this.currentByte++);
return data;
}
readUint8() {
let data = this.view.getUint8(this.currentByte++);
return data;
}
readInt16() {
let data = this.view.getInt16(this.currentByte, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
this.currentByte += 2;
return data;
}
readUint16() {
let data = this.view.getUint16(this.currentByte, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
this.currentByte += 2;
return data;
}
readInt32() {
let data = this.view.getInt32(this.currentByte, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
this.currentByte += 4;
return data;
}
readUint32() {
let data = this.view.getUint32(this.currentByte, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
this.currentByte += 4;
return data;
}
readLong() {
let data = this.view.getBigInt64(this.currentByte, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
this.currentByte += 8;
return data;
}
readInt64() {
return this.readLong();
}
readUint64() {
let data = this.view.getBigUint64(this.currentByte, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
this.currentByte += 8;
return data;
}
readFloat32() {
let data = this.view.getFloat32(this.currentByte, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
this.currentByte += 4;
return data;
}
readDouble() {
let data = this.view.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.view.buffer.byteLength - this.currentByte)) {
let errorMessage = `Cannot read string of length ${strLength} at position ${this.currentByte} as it exceeds the end at ${this.view.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;
return string.join('');
}
try {
const string = new Array(strLength - 1).fill('').map(c => String.fromCharCode(this.readUint8()));
this.currentByte += 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;
}
}
}
exports.StreamParserReader = StreamParserReader;