UNPKG

@etothepii/satisfactory-file-parser

Version:

A file parser for satisfactory files. Includes save files and blueprint files.

122 lines (121 loc) 6.2 kB
"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;