@etothepii/satisfactory-file-parser
Version:
A file parser for satisfactory files. Includes save files and blueprint files.
122 lines (121 loc) • 6.2 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SaveStreamReader = void 0;
const pako_1 = __importDefault(require("pako"));
const __1 = require("../..");
const alignment_enum_1 = require("../byte/alignment.enum");
const byte_stream_reader_class_1 = require("./byte-stream-reader.class");
const stream_level_class_1 = require("./stream-level.class");
class SaveStreamReader extends byte_stream_reader_class_1.ByteStreamReader {
constructor(reader, maxBufferThreshold, onCloseCallback = async () => { }) {
super(reader, onCloseCallback, 30000, maxBufferThreshold, alignment_enum_1.Alignment.LITTLE_ENDIAN);
this.compressionInfo = {
packageFileTag: 0,
maxUncompressedChunkContentSize: 0,
chunkHeaderSize: SaveStreamReader.DEFAULT_SATISFACTORY_CHUNK_HEADER_SIZE
};
}
async readHeader() {
const header = {
saveHeaderType: 0,
saveVersion: 0,
buildVersion: 0,
mapName: "DEFAULT",
mapOptions: "",
sessionName: "",
playDurationSeconds: 0,
saveDateTime: "0",
sessionVisibility: 0
};
await this.allocate(400);
console.log(`safely reading header stuff now. ${this.currentByte} ${this.operatingDataView.byteLength} ${this.operatingStreamBuffer.byteLength}`);
header.saveHeaderType = this.readInt32();
header.saveVersion = this.readInt32();
header.buildVersion = this.readInt32();
header.mapName = this.readString();
header.mapOptions = this.readString();
header.sessionName = this.readString();
header.playDurationSeconds = this.readInt32();
const rawSaveDateTimeInTicks = this.readLong();
const unixMilliseconds = (rawSaveDateTimeInTicks - SaveStreamReader.EPOCH_TICKS) / 10000n;
header.saveDateTime = unixMilliseconds.toString();
header.sessionVisibility = this.readByte();
if (header.saveHeaderType >= 7) {
header.fEditorObjectVersion = this.readInt32();
}
if (header.saveHeaderType >= 8) {
header.rawModMetadataString = this.readString();
try {
header.modMetadata = JSON.parse(header.rawModMetadataString);
}
catch (error) {
}
header.isModdedSave = this.readInt32();
}
if (header.saveHeaderType >= 10) {
header.saveIdentifier = this.readString();
}
if (header.saveVersion >= 21) {
}
else {
throw new __1.UnsupportedVersionError("The save version is too old to support encoding currently. Save in newer game version.");
}
return header;
}
async readBodyChunk() {
await this.allocate(this.compressionInfo.chunkHeaderSize);
this.operatingStreamBuffer = this.operatingStreamBuffer.slice(this.currentByte);
this.operatingDataView = new DataView(this.operatingStreamBuffer.buffer);
this.currentByte = 0;
if (this.compressionInfo.packageFileTag <= 0) {
this.compressionInfo.packageFileTag = this.operatingDataView.getInt32(0, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
}
if (this.compressionInfo.maxUncompressedChunkContentSize <= 0) {
this.compressionInfo.maxUncompressedChunkContentSize = this.operatingDataView.getInt32(8, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
}
const chunkCompressedLength = this.operatingDataView.getInt32(32, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
const chunkUncompressedLength = this.operatingDataView.getInt32(40, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
this.currentByte = this.compressionInfo.chunkHeaderSize;
await this.allocate(chunkCompressedLength);
let currentChunk = this.operatingStreamBuffer.slice(this.currentByte, this.currentByte + chunkCompressedLength);
this.currentByte += chunkCompressedLength;
this.totalNumberOfBytesRead += chunkUncompressedLength;
try {
const uncompressedChunk = pako_1.default.inflate(currentChunk);
if (uncompressedChunk.byteLength !== chunkUncompressedLength) {
throw new __1.CorruptSaveError('indicated save body chunk size does not match the inflated result. Save is possibly corrupt.');
}
if (this.debug) {
console.log(`${new Date().toString()}: decompressed a chunk from ${currentChunk.byteLength} to ${uncompressedChunk.byteLength} bytes.`);
}
return uncompressedChunk;
}
catch (err) {
throw new __1.CompressionLibraryError("Failed to inflate compressed save data. " + err);
}
}
debugInfo() {
return `operating buffer size is ${this.operatingStreamBuffer.byteLength}/${this.operatingDataView.byteLength}, at ${this.currentByte} means ${this.getBufferPosition()} pos, with ${this.operatingStreamBuffer.byteLength - this.currentByte} left until end of operating buffer.`;
}
async streamLevelsToOutput(writer, header) {
if (header.saveVersion < 29) {
throw new __1.UnsupportedVersionError('Support for < U6 is not yet implemented.');
}
await this.allocate(100);
const numSubLevels = this.readInt32();
for (let j = 0; j <= numSubLevels; j++) {
let levelName = (j === numSubLevels) ? '' + header.mapName : this.readString();
if (this.debug) {
console.log(`${new Date().toString()}: reading level [${(j)}/${numSubLevels}] ${levelName}`);
}
await stream_level_class_1.StreamLevel.ReadLevelAsync(this, writer, levelName, header.buildVersion);
}
return;
}
}
SaveStreamReader.EPOCH_TICKS = 621355968000000000n;
SaveStreamReader.DEFAULT_SATISFACTORY_CHUNK_HEADER_SIZE = 48;
exports.SaveStreamReader = SaveStreamReader;