unreal.js
Version:
A pak reader for games like VALORANT & Fortnite written in Node.JS
723 lines (722 loc) • 28.8 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.FIoStoreReader = exports.FIoStoreTocResource = exports.EIoStoreTocReadOptions = exports.FIoStoreTocCompressedBlockEntry = exports.FIoStoreTocEntryMeta = exports.FIoStoreTocEntryMetaFlags = exports.FIoOffsetAndLength = exports.FIoStoreTocHeader = exports.EIoStoreTocVersion = void 0;
const Guid_1 = require("../objects/core/misc/Guid");
const IoContainerId_1 = require("./IoContainerId");
const fs = __importStar(require("fs"));
const IoDispatcher_1 = require("./IoDispatcher");
const FByteArchive_1 = require("../reader/FByteArchive");
const Aes_1 = require("../../encryption/aes/Aes");
const Compression_1 = require("../../compression/Compression");
const Utils_1 = require("../../util/Utils");
const GameFile_1 = require("../pak/GameFile");
const IoDirectoryIndex_1 = require("./IoDirectoryIndex");
const Lazy_1 = require("../../util/Lazy");
const AbstractAesVfsReader_1 = require("../vfs/AbstractAesVfsReader");
const Game_1 = require("../versions/Game");
/**
* I/O store container format version
* @enum
*/
var EIoStoreTocVersion;
(function (EIoStoreTocVersion) {
EIoStoreTocVersion[EIoStoreTocVersion["Invalid"] = 0] = "Invalid";
EIoStoreTocVersion[EIoStoreTocVersion["Initial"] = 1] = "Initial";
EIoStoreTocVersion[EIoStoreTocVersion["DirectoryIndex"] = 2] = "DirectoryIndex";
EIoStoreTocVersion[EIoStoreTocVersion["PartitionSize"] = 3] = "PartitionSize";
EIoStoreTocVersion[EIoStoreTocVersion["PerfectHash"] = 4] = "PerfectHash";
EIoStoreTocVersion[EIoStoreTocVersion["PerfectHashWithOverflow"] = 5] = "PerfectHashWithOverflow";
EIoStoreTocVersion[EIoStoreTocVersion["LatestPlusOne"] = 6] = "LatestPlusOne";
EIoStoreTocVersion[EIoStoreTocVersion["Latest"] = 5] = "Latest";
})(EIoStoreTocVersion = exports.EIoStoreTocVersion || (exports.EIoStoreTocVersion = {}));
/**
* I/O Store TOC header
*/
class FIoStoreTocHeader {
/**
* Creates an instance using an UE4 Reader
* @param {FArchive} Ar UE4 Reader to use
* @constructor
* @public
*/
constructor(Ar) {
/**
* Toc magic
* @type {Buffer}
* @public
*/
this.tocMagic = Buffer.alloc(16);
/**
* Reserved8
* @type {Array<bigint>}
* @public
*/
this.reserved8 = new Array(5);
Ar.readToBuffer(this.tocMagic);
if (!this.checkMagic())
throw new Error("TOC header magic mismatch");
this.version = Ar.readUInt8();
if (!Object.values(EIoStoreTocVersion).includes(this.version)) {
const latest = EIoStoreTocVersion.Latest;
console.warn(`Unsupported TOC version ${this.version}, falling back to latest (${latest})`);
this.version = latest;
}
this.reserved0 = Ar.readUInt8();
this.reserved1 = Ar.readUInt16();
this.tocHeaderSize = Ar.readUInt32();
this.tocEntryCount = Ar.readUInt32();
this.tocCompressedBlockEntryCount = Ar.readUInt32();
this.tocCompressedBlockEntrySize = Ar.readUInt32();
this.compressionMethodNameCount = Ar.readUInt32();
this.compressionMethodNameLength = Ar.readUInt32();
this.compressionBlockSize = Ar.readUInt32();
this.directoryIndexSize = Ar.readUInt32();
this.partitionCount = Ar.readUInt32();
this.containerId = IoContainerId_1.createFIoContainerId(Ar);
this.encryptionKeyGuid = new Guid_1.FGuid(Ar);
this.containerFlags = Ar.readUInt8();
this.reserved3 = Ar.readUInt8();
this.reserved4 = Ar.readUInt16();
this.tocChunkPerfectHashSeedsCount = Ar.readUInt32();
this.partitionSize = Ar.readUInt64();
this.tocChunksWithoutPerfectHashCount = Ar.readUInt32();
this.reserved7 = Ar.readUInt32();
const len = this.reserved8.length;
for (let i = 0; i < len; i++) {
this.reserved8[i] = Ar.readUInt64();
}
}
/**
* Creates toc magic
* @returns {Buffer} Magic
* @public
*/
makeMagic() {
this.tocMagic = Buffer.from(FIoStoreTocHeader.TocMagicImg);
}
/**
* Checks magic
* @returns {boolean} Result
* @public
*/
checkMagic() {
return this.tocMagic.equals(Buffer.from(FIoStoreTocHeader.TocMagicImg));
}
}
exports.FIoStoreTocHeader = FIoStoreTocHeader;
/**
* Toc magic template
* @type {string}
* @public
* @static
*/
FIoStoreTocHeader.TocMagicImg = "-==--==--==--==-";
/**
* Combined offset and length
*/
class FIoOffsetAndLength {
/**
* Creates an instance using an UE4 Reader
* @param {FArchive} Ar UE4 Reader to use
* @constructor
* @public
*/
constructor(Ar = null) {
this.offsetAndLength = Buffer.alloc(5 + 5);
if (Ar)
Ar.readToBuffer(this.offsetAndLength);
}
/**
* Offset
* @type {bigint}
* @public
*/
get offset() {
return BigInt(this.offsetAndLength[4])
| BigInt(this.offsetAndLength[3]) << 8n
| BigInt(this.offsetAndLength[2]) << 16n
| BigInt(this.offsetAndLength[1]) << 24n
| BigInt(this.offsetAndLength[0]) << 32n;
}
/**
* Length
* @type {bigint}
* @public
*/
get length() {
return BigInt(this.offsetAndLength[9])
| BigInt(this.offsetAndLength[8]) << 8n
| BigInt(this.offsetAndLength[7]) << 16n
| BigInt(this.offsetAndLength[6]) << 24n
| BigInt(this.offsetAndLength[5]) << 32n;
}
}
exports.FIoOffsetAndLength = FIoOffsetAndLength;
/**
* FIoStoreTocEntryMetaFlags
* @enum
*/
var FIoStoreTocEntryMetaFlags;
(function (FIoStoreTocEntryMetaFlags) {
FIoStoreTocEntryMetaFlags[FIoStoreTocEntryMetaFlags["None"] = 0] = "None";
FIoStoreTocEntryMetaFlags[FIoStoreTocEntryMetaFlags["Compressed"] = 1] = "Compressed";
FIoStoreTocEntryMetaFlags[FIoStoreTocEntryMetaFlags["MemoryMapped"] = 2] = "MemoryMapped";
})(FIoStoreTocEntryMetaFlags = exports.FIoStoreTocEntryMetaFlags || (exports.FIoStoreTocEntryMetaFlags = {}));
/**
* TOC entry meta data
*/
class FIoStoreTocEntryMeta {
/**
* Creates an instance using an UE4 Reader
* @param {FArchive} Ar UE4 Reader to use
* @constructor
* @public
*/
constructor(Ar) {
this.chunkHash = new IoDispatcher_1.FIoChunkHash(Ar);
this.flags = Ar.readUInt8();
}
}
exports.FIoStoreTocEntryMeta = FIoStoreTocEntryMeta;
/**
* Compression block entry
*/
class FIoStoreTocCompressedBlockEntry {
/**
* Creates an instance using an UE4 Reader
* @param {FArchive} Ar UE4 Reader to use
* @constructor
* @public
*/
constructor(Ar) {
/**
* Data
* 5 bytes offset, 3 bytes for size / uncompressed size and 1 byte for compression method -> Buffer size
* @type {Buffer}
* @public
*/
this.data = Buffer.alloc(5 + 3 + 3 + 1);
Ar.readToBuffer(this.data);
}
/**
* Offset
* @type {bigint}
* @public
*/
get offset() {
const offset = this.data.readBigUInt64LE();
return offset & FIoStoreTocCompressedBlockEntry.OffsetMask;
}
/**
* Compressed size
* @type {number}
* @public
*/
get compressedSize() {
const size = this.data.readUInt32LE(4);
return size >> FIoStoreTocCompressedBlockEntry.SizeShift;
}
/**
* Uncompressed size
* @type {number}
* @public
*/
get uncompressedSize() {
const size = this.data.readUInt32LE(2 * 4);
return size & FIoStoreTocCompressedBlockEntry.SizeMask;
}
/**
* Compression method index
* @type {number}
* @public
*/
get compressionMethodIndex() {
const index = this.data.readUInt32LE(2 * 4);
return index >> FIoStoreTocCompressedBlockEntry.SizeBits;
}
}
exports.FIoStoreTocCompressedBlockEntry = FIoStoreTocCompressedBlockEntry;
/**
* OffsetBits
* @type {number}
* @public
* @static
*/
FIoStoreTocCompressedBlockEntry.OffsetBits = 40;
/**
* OffsetMask
* @type {bigint}
* @public
* @static
*/
FIoStoreTocCompressedBlockEntry.OffsetMask = (1n << BigInt(FIoStoreTocCompressedBlockEntry.OffsetBits)) - 1n;
/**
* SizeBits
* @type {number}
* @public
* @static
*/
FIoStoreTocCompressedBlockEntry.SizeBits = 24;
/**
* SizeMask
* @type {number}
* @public
* @static
*/
FIoStoreTocCompressedBlockEntry.SizeMask = (1 << FIoStoreTocCompressedBlockEntry.SizeBits) - 1;
/**
* SizeShift
* @type {number}
* @public
* @static
*/
FIoStoreTocCompressedBlockEntry.SizeShift = 8;
/**
* TOC resource read options
* @enum
*/
var EIoStoreTocReadOptions;
(function (EIoStoreTocReadOptions) {
EIoStoreTocReadOptions[EIoStoreTocReadOptions["Default"] = 0] = "Default";
EIoStoreTocReadOptions[EIoStoreTocReadOptions["ReadDirectoryIndex"] = 1] = "ReadDirectoryIndex";
EIoStoreTocReadOptions[EIoStoreTocReadOptions["ReadTocMeta"] = 2] = "ReadTocMeta";
EIoStoreTocReadOptions[EIoStoreTocReadOptions["ReadAll"] = 3] = "ReadAll";
})(EIoStoreTocReadOptions = exports.EIoStoreTocReadOptions || (exports.EIoStoreTocReadOptions = {}));
/**
* Container TOC data
*/
class FIoStoreTocResource {
constructor() {
/**
* Chunk perfect hash seeds
* @type {Array<number> | null}
* @public
*/
this.chunkPerfectHashSeeds = null;
/**
* Chunk indices without perfect hash
* @type {Array<number> | null}
* @public
*/
this.chunkIndicesWithoutPerfectHash = null;
/**
* chunkIdToIndex (sort of: Collection<string, number>)
* @type {object}
* @public
*/
this.chunkIdToIndex = {};
}
/**
* Reads a toc buffer
* @param {FArchive} tocBuffer Toc buffer to read
* @param {EIoStoreTocReadOptions} readOptions Config for reading
* @returns {void}
* @public
*/
read(tocBuffer, readOptions) {
// Header
this.header = new FIoStoreTocHeader(tocBuffer);
if (this.header.tocHeaderSize !== 144 /*sizeof(FIoStoreTocHeader)*/) {
throw new Error("TOC header size mismatch"); //throw new FIoStatusException(EIoErrorCode.CorruptToc, "TOC header size mismatch", tocBuffer)
}
if (this.header.tocCompressedBlockEntrySize !== 12 /*sizeof(FIoStoreTocCompressedBlockEntry)*/) {
throw new Error("TOC compressed block entry size mismatch"); //throw new FIoStatusException(EIoErrorCode.CorruptToc, "TOC compressed block entry size mismatch", tocBuffer)
}
if (this.header.version < EIoStoreTocVersion.DirectoryIndex) {
throw new Error("Outdated TOC header version"); //throw new FIoStatusException(EIoErrorCode.CorruptToc, "Outdated TOC header version", tocBuffer)
}
if (this.header.version < EIoStoreTocVersion.PartitionSize) {
this.header.partitionCount = 1;
this.header.partitionSize = 0xfffffffffffffffn;
}
// Chunk IDs
const _len1 = this.header.tocEntryCount;
this.chunkIds = new Array(_len1);
if (this.header.version >= EIoStoreTocVersion.PerfectHash) {
this.chunkIdToIndex = {};
for (let i = 0; i < _len1; ++i) {
this.chunkIds[i] = new IoDispatcher_1.FIoChunkId(tocBuffer);
}
}
else {
for (let i = 0; i < _len1; i++) {
const id = new IoDispatcher_1.FIoChunkId(tocBuffer);
this.chunkIds[i] = id;
this.chunkIdToIndex[id.id.toString("base64")] = i;
}
}
// Chunk offsets
this.chunkOffsetLengths = new Array(_len1);
for (let i = 0; i < _len1; i++) {
this.chunkOffsetLengths[i] = new FIoOffsetAndLength(tocBuffer);
}
// Chunk perfect hash map
let perfectHashSeedsCount = 0;
let chunksWithoutPerfectHashCount = 0;
if (this.header.version >= EIoStoreTocVersion.PerfectHashWithOverflow) {
perfectHashSeedsCount = this.header.tocChunkPerfectHashSeedsCount;
chunksWithoutPerfectHashCount = this.header.tocChunksWithoutPerfectHashCount;
}
else if (this.header.version >= EIoStoreTocVersion.PerfectHash) {
perfectHashSeedsCount = this.header.tocChunkPerfectHashSeedsCount;
}
if (perfectHashSeedsCount > 0) {
this.chunkPerfectHashSeeds = new Array(perfectHashSeedsCount);
for (let i = 0; i < perfectHashSeedsCount; ++i) {
this.chunkPerfectHashSeeds[i] = tocBuffer.readInt32();
}
}
if (chunksWithoutPerfectHashCount > 0) {
this.chunkIndicesWithoutPerfectHash = new Array(chunksWithoutPerfectHashCount);
for (let i = 0; i < chunksWithoutPerfectHashCount; ++i) {
this.chunkIndicesWithoutPerfectHash[i] = tocBuffer.readInt32();
}
for (let chunkIndexWithoutPerfectHash of this.chunkIndicesWithoutPerfectHash) {
this.chunkIdToIndex[this.chunkIds[chunkIndexWithoutPerfectHash].id.toString("base64")] = chunkIndexWithoutPerfectHash;
}
}
// Compression blocks
const _len2 = this.header.tocCompressedBlockEntryCount;
this.compressionBlocks = new Array(_len2);
for (let i = 0; i < _len2; i++) {
this.compressionBlocks[i] = new FIoStoreTocCompressedBlockEntry(tocBuffer);
}
// Compression methods
this.compressionMethods = ["None"];
for (let i = 0; i < this.header.compressionMethodNameCount; i++) {
const compressionMethodName = tocBuffer.read(this.header.compressionMethodNameLength);
let length = 0;
while (compressionMethodName[length] !== 0) {
++length;
}
this.compressionMethods[1 + i] = (compressionMethodName.toString("utf-8", 0, length));
}
// Chunk block signatures
if (this.header.containerFlags & IoDispatcher_1.EIoContainerFlags.Signed) {
const hashSize = tocBuffer.readInt32();
tocBuffer.pos += hashSize; // actually: const tocSignature = tocBuffer.read(hashSize)
tocBuffer.pos += hashSize; // actually: const blockSignature = tocBuffer.read(hashSize)
/*this.chunkBlockSignatures = new Array(this.header.tocCompressedBlockEntryCount)
for (let i = 0; i < this.header.tocCompressedBlockEntryCount; i++) {
this.chunkBlockSignatures[i] = tocBuffer.read(20)
}*/
tocBuffer.pos += this.header.tocCompressedBlockEntryCount * 20;
// You could verify hashes here but nah
} /* else {
this.chunkBlockSignatures = []
}*/
// Directory index
if (this.header.containerFlags & IoDispatcher_1.EIoContainerFlags.Indexed
&& this.header.directoryIndexSize > 0) {
if (readOptions & EIoStoreTocReadOptions.ReadDirectoryIndex) {
this.directoryIndexBuffer = tocBuffer.read(this.header.directoryIndexSize & 0xFFFFFFFF);
}
else {
tocBuffer.pos += this.header.directoryIndexSize;
}
}
// Meta
if ((readOptions & EIoStoreTocReadOptions.ReadTocMeta)) {
this.chunkMetas = new Array(_len1);
for (let i = 0; i < _len1; i++) {
this.chunkMetas[i] = new FIoStoreTocEntryMeta(tocBuffer);
}
}
else {
this.chunkMetas = [];
}
}
/**
* getTocEntryIndex
* @param {FIoChunkId} chunkId Chunk ID
* @returns {number} Index
* @public
*/
getTocEntryIndex(chunkId) {
const chunkPerfectHashSeeds = this.chunkPerfectHashSeeds;
if (chunkPerfectHashSeeds != null) {
const chunkCount = this.header.tocEntryCount;
if (chunkCount === 0)
return -1;
const seedCount = chunkPerfectHashSeeds.length;
const seedIndex = chunkId.hashWithSeed(0) % BigInt(seedCount);
const seed = chunkPerfectHashSeeds[Number(seedIndex)];
if (seed === 0)
return -1;
let slot;
if (seed < 0) {
const seedAsIndex = -seed - 1;
if (seedAsIndex < chunkCount) {
slot = seedAsIndex;
}
else {
// Entry without perfect hash
return this.chunkIdToIndex[chunkId.id.toString("base64")] || -1;
}
}
else {
slot = Number(chunkId.hashWithSeed(seed) % BigInt(chunkCount));
}
if (this.chunkIds[slot]?.equals(chunkId))
return slot;
return -1;
}
return this.chunkIdToIndex[chunkId.id.toString("base64")] || -1;
}
/**
* getOffsetAndLength
* @param {FIoChunkId} chunkId Chunk ID
* @returns {FIoOffsetAndLength} Offset and length
* @public
*/
getOffsetAndLength(chunkId) {
const tocEntryIndex = this.getTocEntryIndex(chunkId);
return tocEntryIndex !== -1 ? this.chunkOffsetLengths[tocEntryIndex] : null;
}
}
exports.FIoStoreTocResource = FIoStoreTocResource;
/**
* CompressionMethodNameLen
* @type {number}
* @public
* @static
*/
FIoStoreTocResource.CompressionMethodNameLen = 32;
/**
* FIoStoreReader
*/
class FIoStoreReader extends AbstractAesVfsReader_1.AbstractAesVfsReader {
constructor(path, versions) {
super(path, versions);
/**
* Toc
* @type {FIoStoreTocResource}
* @private
*/
this.toc = new FIoStoreTocResource();
/**
* decryptionKey
* @type {?Buffer}
* @private
*/
this.decryptionKey = null;
/**
* containerFileHandles
* @type {Array<number>}
* @private
*/
this.containerFileHandles = [];
/**
* directoryIndexReader
* @type {Lazy<FIoDirectoryIndexReader>}
* @public
*/
this.directoryIndexReader = new Lazy_1.Lazy(() => {
if ((this.toc.header.containerFlags & IoDispatcher_1.EIoContainerFlags.Indexed) && this.toc.directoryIndexBuffer != null) {
const out = new IoDirectoryIndex_1.FIoDirectoryIndexReader(this.toc.directoryIndexBuffer, this.decryptionKey);
this.toc.directoryIndexBuffer = null;
return out;
}
return null;
});
}
get hasDirectoryIndex() {
return this.directoryIndexReader != null || this.toc.directoryIndexBuffer != null;
}
/**
* Initializes this
* @param {FIoStoreEnvironment} environment Environment to use
* @param {UnrealMap<FGuid, Buffer>} decryptionKeys Decryption keys to use
* @param {number} readOptions Options for reading io store toc
* @returns {void}
* @public
*/
initialize(environment, decryptionKeys, readOptions) {
this.environment = environment;
this.toc.read(new FByteArchive_1.FByteArchive(fs.readFileSync(this.environment.path + ".utoc")), readOptions);
for (let partitionIndex = 0; partitionIndex < this.toc.header.partitionCount; ++partitionIndex) {
let containerFilePath = "";
containerFilePath += this.environment.path;
if (partitionIndex > 0) {
containerFilePath += `_s${partitionIndex}`;
}
containerFilePath += ".ucas";
try {
this.containerFileHandles[partitionIndex] = fs.openSync(containerFilePath, "rs");
}
catch (err) {
throw new Error(`Failed to open IoStore container file '${containerFilePath}'`);
}
}
if (this.isEncrypted) {
const findKey = decryptionKeys.get(this.toc.header.encryptionKeyGuid);
if (!findKey) {
throw new Error(`Missing decryption key for IoStore container file '${environment.path}'`);
}
this.decryptionKey = findKey;
}
console.log("IoStore \"%s\": %d %s, version %d", environment.path, this.toc.header.tocEntryCount, this.decryptionKey ? "encrypted chunks" : "chunks", this.toc.header.version);
}
extract(gameFile) {
if (gameFile.ioChunkId == null && gameFile.pakFileName != this.name)
throw new TypeError(`Wrong I/O store reader, required ${gameFile.pakFileName}, this is ${this.name}`);
return this.read(gameFile.ioChunkId);
}
readIndex() {
const start = Date.now();
const files = [];
const directoryIndex = this.directoryIndexReader.value;
if (directoryIndex != null) {
const exportBundleDataChunkType = this.game >= Game_1.Game.GAME_UE5_BASE ? IoDispatcher_1.EIoChunkType5.ExportBundleData : IoDispatcher_1.EIoChunkType.ExportBundleData;
directoryIndex.iterateDirectoryIndex(IoDispatcher_1.FIoDirectoryIndexHandle.rootDirectory(), "", (fileName, tocEntryIndex) => {
const chunkId = this.toc.chunkIds[tocEntryIndex];
if (chunkId.chunkType === exportBundleDataChunkType)
files.push(GameFile_1.GameFile.createFromIoStoreFile(fileName, this.name, chunkId));
return true;
});
this.mountPoint = directoryIndex.mountPoint;
}
// Print statistics
const chunks = this.toc.header.tocEntryCount;
const encrypted = this.isEncrypted ? "encrypted chunks" : "chunks";
const v = this.toc.header.version;
const time = Date.now() - start;
console.log(`IoStore \"${this.path}\": ${chunks} ${encrypted}, version ${v} in ${time}ms`);
this.files = files;
return files;
}
indexCheckBytes() {
return this.toc.directoryIndexBuffer || Buffer.alloc(AbstractAesVfsReader_1.AbstractAesVfsReader.MAX_MOUNTPOINT_TEST_LENGTH);
}
get isEncrypted() {
return (this.toc.header.containerFlags & IoDispatcher_1.EIoContainerFlags.Encrypted) != 0;
}
/**
* Container ID
* @type {bigint}
* @public
*/
get containerId() {
return this.toc.header.containerId;
}
/**
* Container Flags
* @type {EIoContainerFlags}
* @public
*/
get containerFlags() {
return this.toc.header.containerFlags;
}
/**
* Encryption key guid
* @type {FGuid}
* @public
*/
get encryptionKeyGuid() {
return this.toc.header.encryptionKeyGuid;
}
/**
* Reads chunk id
* @param {FIoChunkId} chunkId ID to read
* @returns {Buffer} Read bytes
* @public
*/
read(chunkId /*, options: FIoReadOptions = FIoReadOptions()*/) {
const offsetAndLength = this.toc.getOffsetAndLength(chunkId);
if (!offsetAndLength)
throw new Error("Unknown chunk ID");
const _offset = offsetAndLength.offset;
const _length = offsetAndLength.length;
const offset = Number(_offset);
const length = Number(_length);
const threadBuffers = new FThreadBuffers();
const compressionBlockSize = this.toc.header.compressionBlockSize;
const firstBlockIndex = Math.floor(offset / compressionBlockSize);
const lastBlockIndex = Math.floor((Utils_1.Utils.alignBigInt(_offset + _length, BigInt(compressionBlockSize)) - 1) / compressionBlockSize);
let offsetInBlock = offset % compressionBlockSize;
const dst = Buffer.alloc(length);
let dstOff = 0;
let remainingSize = length;
let blockIndex = firstBlockIndex; // 'while()' seems to be faster than: 'for (let blockIndex = firstBlockIndex; blockIndex <= lastBlockIndex; ++blockIndex)'
while (blockIndex <= lastBlockIndex) {
const compressionBlock = this.toc.compressionBlocks[blockIndex];
const rawSize = Utils_1.Utils.align(compressionBlock.compressedSize, Aes_1.Aes.BLOCK_SIZE);
if (threadBuffers.compressedBuffer == null || threadBuffers.compressedBuffer.length < rawSize) {
threadBuffers.compressedBuffer = Buffer.alloc(rawSize);
}
const uncompressedSize = compressionBlock.uncompressedSize;
if (threadBuffers.uncompressedBuffer == null || threadBuffers.uncompressedBuffer.length < uncompressedSize) {
threadBuffers.uncompressedBuffer = Buffer.alloc(uncompressedSize);
}
const partitionIndex = Math.floor(Number(compressionBlock.offset / this.toc.header.partitionSize));
const partitionOffset = Number(compressionBlock.offset % this.toc.header.partitionSize);
const fileHandle = this.containerFileHandles[partitionIndex];
fs.readSync(fileHandle, threadBuffers.compressedBuffer, 0, rawSize, partitionOffset);
if (this.toc.header.containerFlags & IoDispatcher_1.EIoContainerFlags.Encrypted) {
threadBuffers.compressedBuffer = Aes_1.Aes.decrypt(threadBuffers.compressedBuffer, this.decryptionKey);
}
let src;
if (compressionBlock.compressionMethodIndex === 0) {
src = threadBuffers.compressedBuffer;
}
else {
const compressionMethod = this.toc.compressionMethods[compressionBlock.compressionMethodIndex];
try {
Compression_1.Compression.uncompress(compressionMethod, threadBuffers.uncompressedBuffer, 0, uncompressedSize, threadBuffers.compressedBuffer, 0, compressionBlock.compressedSize);
src = threadBuffers.uncompressedBuffer;
}
catch (e) {
throw new Error("Failed uncompressing block");
}
}
const sizeInBlock = Math.min(compressionBlockSize - offsetInBlock, remainingSize);
src.copy(dst, dstOff, offsetInBlock, offsetInBlock + sizeInBlock);
offsetInBlock = 0;
remainingSize -= sizeInBlock;
dstOff += sizeInBlock;
++blockIndex;
}
return dst;
}
/**
* Gets files
* @returns {Array<GameFile>} Files
* @public
*/
getFiles() {
const files = new Array();
this.directoryIndexReader.value?.iterateDirectoryIndex(IoDispatcher_1.FIoDirectoryIndexHandle.rootDirectory(), "", (filename, tocEntryIndex) => {
const chunkId = this.toc.chunkIds[tocEntryIndex];
if (chunkId.chunkType === IoDispatcher_1.EIoChunkType.ExportBundleData) {
files.push(GameFile_1.GameFile.createFromIoStoreFile(filename, this.environment.path, chunkId));
}
return true;
});
return files;
}
}
exports.FIoStoreReader = FIoStoreReader;
/**
* FThreadBuffers
*/
class FThreadBuffers {
}