UNPKG

unreal.js

Version:

A pak reader for games like VALORANT & Fortnite written in Node.JS

281 lines (279 loc) 11.6 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.FLoadedContainer = exports.FPackageStore = exports.FScriptObjectEntry = void 0; const IoDispatcher_1 = require("../io/IoDispatcher"); const FNameMap_1 = require("./FNameMap"); const AsyncLoading2_1 = require("./AsyncLoading2"); const FByteArchive_1 = require("../reader/FByteArchive"); const UnrealMap_1 = require("../../util/UnrealMap"); const collection_1 = __importDefault(require("@discordjs/collection")); const IoContainerHeader_1 = require("../io/IoContainerHeader"); const FName_1 = require("../objects/uobject/FName"); const Pair_1 = require("../../util/Pair"); const NameTypes_1 = require("../objects/uobject/NameTypes"); const Game_1 = require("../versions/Game"); const IoStore_1 = require("../io/IoStore"); /** * FScriptObjectEntry */ class FScriptObjectEntry { /** * Creates an instance using an UE4 Reader * @param {FArchive} Ar UE4 Reader to use * @param {Array<string>} nameMap Name map * @constructor * @public */ constructor(Ar, nameMap) { this.objectName = new NameTypes_1.FMinimalName(Ar, nameMap); this.globalIndex = new AsyncLoading2_1.FPackageObjectIndex(Ar); this.outerIndex = new AsyncLoading2_1.FPackageObjectIndex(Ar); this.cdoClassIndex = new AsyncLoading2_1.FPackageObjectIndex(Ar); } } exports.FScriptObjectEntry = FScriptObjectEntry; /** * FPackageStore (I/O) * @extends {FOnContainerMountedListener} */ class FPackageStore extends IoDispatcher_1.FOnContainerMountedListener { /** * Creates instance using values * @param {FileProvider} provider File provider to use */ constructor(provider) { super(); /** * Loaded containers * @type {Collection<bigint, FLoadedContainer>} * @public */ this.loadedContainers = new collection_1.default(); /** * Package Name Maps Critical * @type {{}} * @public */ this.packageNameMapsCritical = {}; /** * Current culture names * @type {Array<string>} * @public currentCultureNames: string[] = []*/ /** * Store entries * @type {Collection<bigint, FFilePackageStoreEntry>} * @public */ this.storeEntries = new collection_1.default(); /** * Redirected packages * @type {Collection<bigint, Pair<FName, bigint>>} * @public */ this.redirectedPackages = new collection_1.default(); /** * Script object entries * @type {Array<FScriptObjectEntry>} * @public scriptObjectEntries: FScriptObjectEntry[]*/ /** * Script object entries map * @type {UnrealMap<FPackageObjectIndex, FScriptObjectEntry>} * @public */ this.scriptObjectEntries = new UnrealMap_1.UnrealMap(); this.provider = provider; let initialLoadArchive; const globalNameMap = new FNameMap_1.FNameMap(); if (this.provider.game >= Game_1.Game.GAME_UE5_BASE) { initialLoadArchive = new FByteArchive_1.FByteArchive(this.provider.saveChunk(IoDispatcher_1.createIoChunkId(0n, 0, IoDispatcher_1.EIoChunkType5.ScriptObjects))); globalNameMap.load(initialLoadArchive, AsyncLoading2_1.FMappedName_EType.Global); } else { const nameBuffer = this.provider.saveChunk(IoDispatcher_1.createIoChunkId(0n, 0, IoDispatcher_1.EIoChunkType.LoaderGlobalNames)); const hashBuffer = this.provider.saveChunk(IoDispatcher_1.createIoChunkId(0n, 0, IoDispatcher_1.EIoChunkType.LoaderGlobalNameHashes)); globalNameMap.load(nameBuffer, hashBuffer, AsyncLoading2_1.FMappedName_EType.Global); initialLoadArchive = new FByteArchive_1.FByteArchive(this.provider.saveChunk(IoDispatcher_1.createIoChunkId(0n, 0, IoDispatcher_1.EIoChunkType.LoaderInitialLoadMeta))); } const numScriptObjects = initialLoadArchive.readInt32(); for (let i = 0; i < numScriptObjects; ++i) { const entry = new FScriptObjectEntry(initialLoadArchive, globalNameMap.nameEntries); const mappedName = AsyncLoading2_1.FMappedName.fromMinimalName(entry.objectName); if (!mappedName.isGlobal()) throw new Error("FMappedName must be global."); entry.objectName = globalNameMap.getMinimalName(mappedName); this.scriptObjectEntries.set(entry.globalIndex, entry); } // currentCultureNames.add(Locale.getDefault().toString().replace('_', '-')) this.loadContainers(this.provider.mountedPaks.filter(p => p instanceof IoStore_1.FIoStoreReader)); } /** * Sets up culture * @returns {void} * @public setupCulture() { this.currentCultureNames = [osLocale.sync().toString().replace("_", "-")] }*/ /** * Sets up initial load data * @returns {void} * @public setupInitialLoadData() { const initialLoadIoBuffer = this.provider.saveChunk(createIoChunkId(0n, 0, EIoChunkType.LoaderInitialLoadMeta)) const initialLoadArchive = new FByteArchive(initialLoadIoBuffer) const numScriptObjects = initialLoadArchive.readInt32() this.scriptObjectEntries = new Array(numScriptObjects) for (let i = 0; i < numScriptObjects; ++i) { const obj = new FScriptObjectEntry(initialLoadArchive, this.globalNameMap.nameEntries) const mappedName = FMappedName.fromMinimalName(obj.objectName) if (!mappedName.isGlobal()) throw new ParserException("FMappedName must be global", initialLoadArchive) obj.objectName = this.globalNameMap.getMinimalName(mappedName) this.scriptObjectEntriesMap.set(obj.globalIndex, obj) this.scriptObjectEntries[i] = obj } }*/ /** * Loads containers * @param {Array<FIoStoreReader>} containers Containers to load * @returns {void} * @public */ loadContainers(containers) { const invalidId = 0xffffffffffffffffn; const containersToLoad = containers.filter(it => it.containerId !== invalidId); if (!containersToLoad.length) return; const start = Date.now(); console.log(`Loading ${containersToLoad.length} mounted containers...`); for (const container of containersToLoad) { const containerId = container.containerId; let loadedContainer = this.loadedContainers.get(containerId); if (!loadedContainer) { const cont = new FLoadedContainer(); this.loadedContainers.set(containerId, cont); loadedContainer = cont; } const headerChunkId = IoDispatcher_1.createIoChunkId(containerId, 0, this.provider.game >= Game_1.Game.GAME_UE5_BASE ? IoDispatcher_1.EIoChunkType5.ContainerHeader : IoDispatcher_1.EIoChunkType.ContainerHeader); const ioBuffer = container.read(headerChunkId); const containerHeader = new IoContainerHeader_1.FIoContainerHeader(new FByteArchive_1.FByteArchive(ioBuffer, this.provider.versions)); loadedContainer.containerNameMap = containerHeader.redirectsNameMap; loadedContainer.packageCount = containerHeader.packageIds.length; loadedContainer.storeEntries = containerHeader.storeEntries; for (const index in loadedContainer.storeEntries) { this.storeEntries.set(containerHeader.packageIds[index], loadedContainer.storeEntries[index]); } /*let localizedPackages: FSourceToLocalizedPackageIdMap = null for (const cultureName of this.currentCultureNames) { localizedPackages = containerHeader.culturePackageMap.get(cultureName) if (localizedPackages) break } if (localizedPackages) { for (const pair of localizedPackages) { this.redirectedPackages.set(pair.key, pair.value) } }*/ for (const redirect of containerHeader.packageRedirects) { const sourcePackageName = redirect.sourcePackageName != null ? containerHeader.redirectsNameMap.getName(redirect.sourcePackageName) ?? FName_1.FName.NAME_None : FName_1.FName.NAME_None; this.redirectedPackages.set(redirect.sourcePackageId, new Pair_1.Pair(sourcePackageName, redirect.targetPackageId)); } } //this.applyRedirects(this.redirectedPackages) console.log(`Loaded mounted containers in ${Date.now() - start}ms!`); } /** * On container mounted 'event' * @param {FIoStoreReader} container Container to call * @returns {void} * @public */ onContainerMounted(container) { this.loadContainers([container]); } /** * Applies store redirects * @param {UnrealMap<bigint, bigint>} redirects Redirects to apply * @returns {void} * @public */ applyRedirects(redirects) { if (!redirects.size) return; for (const [sourceId, redirectId] of redirects) { if (redirectId === 0xffffffffffffffffn) throw new Error("Redirect must be valid"); this.storeEntries.set(sourceId, this.storeEntries.get(redirectId)); } for (const storeEntry of this.storeEntries.values()) { storeEntry.importedPackages.forEach((importedPackageId, index) => { storeEntry.importedPackages[index] = redirects.get(importedPackageId); }); } } /** * Gets an store entry * @param {bigint} packageId ID of store entry to get * @returns {FFilePackageStoreEntry} * @public */ findStoreEntry(packageId) { return this.storeEntries.get(packageId); } /** * Gets redirected package id * @param {bigint} packageId ID of redirect to get * @returns {Collection<bigint, bigint>} * @public */ getRedirectedPackageId(packageId) { return this.redirectedPackages.get(packageId); } } exports.FPackageStore = FPackageStore; /** * FLoadedContainer */ class FLoadedContainer { constructor() { /** * Container name map * @type {FNameMap} * @public */ this.containerNameMap = new FNameMap_1.FNameMap(); /** * Store entries * @type {Array<FFilePackageStoreEntry>} * @public */ this.storeEntries = []; /** * Package count * @type {number} * @public */ this.packageCount = 0; /** * Order * @type {number} * @public */ this.order = 0; /** * bValid * @type {boolean} * @public */ this.bValid = false; } } exports.FLoadedContainer = FLoadedContainer;