UNPKG

unreal.js

Version:

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

397 lines (396 loc) 18.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.FArc = exports.FImportedPackage = exports.IoPackage = void 0; const Package_1 = require("./Package"); const AsyncLoading2_1 = require("../asyncloading2/AsyncLoading2"); const FNameMap_1 = require("../asyncloading2/FNameMap"); const UObject_1 = require("./exports/UObject"); const FByteArchive_1 = require("../reader/FByteArchive"); const FName_1 = require("../objects/uobject/FName"); const EPackageFlags_1 = require("../objects/uobject/EPackageFlags"); const Exceptions_1 = require("../../exceptions/Exceptions"); const UEnum_1 = require("./exports/UEnum"); const Pair_1 = require("../../util/Pair"); const sprintf_js_1 = require("sprintf-js"); const FExportArchive_1 = require("./reader/FExportArchive"); const Lazy_1 = require("../../util/Lazy"); const Config_1 = require("../../Config"); const Game_1 = require("../versions/Game"); /** * UE4 I/O Package * @extends {Package} */ class IoPackage extends Package_1.Package { /** * Creates an instance * @param {Buffer} uasset Uasset data of package * @param {bigint} packageId ID of package * @param {FFilePackageStoreEntry} storeEntry Store entry * @param {FPackageStore} globalPackageStore Package store * @param {FileProvider} provider Instance of file provider * @param {VersionContainer} versions Version of package */ constructor(uasset, packageId, storeEntry, globalPackageStore, provider, versions = provider.versions) { super("", provider, versions); /** * Imported Export Hashes * @type {Array<bigint>} * @public */ this.importedPublicExportHashes = null; /** * Offset start of bulk data * @type {number} * @public */ this.bulkDataStartOffset = 0; this.packageId = packageId; this.globalPackageStore = globalPackageStore; const Ar = new FByteArchive_1.FByteArchive(uasset, versions); let allExportDataOffset; if (versions.game >= Game_1.Game.GAME_UE5_BASE) { const summary = new AsyncLoading2_1.FZenPackageSummary(Ar); if (summary.bHasVersioningInfo) { const versioningInfo = new AsyncLoading2_1.FZenPackageVersioningInfo(Ar); if (!versions.explicitVer) { versions.ver = versioningInfo.packageVersion.value; versions.customVersions = versioningInfo.customVersions; } } // Name map this.nameMap = new FNameMap_1.FNameMap(); this.nameMap.load(Ar, AsyncLoading2_1.FMappedName_EType.Package); const diskPackageName = this.nameMap.getName(summary.name); this.fileName = diskPackageName.text; this.packageFlags = summary.packageFlags; this.name = this.fileName; // Imported public export hashes Ar.pos = summary.importedPublicExportHashesOffset; const importedPublicExportHashesLen = (summary.importMapOffset - summary.importedPublicExportHashesOffset) / 8; this.importedPublicExportHashes = new Array(importedPublicExportHashesLen); for (let i = 0; i < importedPublicExportHashesLen; ++i) this.importedPublicExportHashes[i] = Ar.readInt64(); // Import map Ar.pos = summary.importMapOffset; const importCount = (summary.exportMapOffset - summary.importMapOffset) / 8; this.importMap = new Array(importCount); for (let i = 0; i < importCount; ++i) this.importMap[i] = new AsyncLoading2_1.FPackageObjectIndex(Ar); // Export map Ar.pos = summary.exportMapOffset; const exportCount = storeEntry.exportCount; //(summary.exportBundleEntriesOffset - summary.exportMapOffset) / FExportMapEntry.SIZE this.exportMap = new Array(exportCount); for (let i = 0; i < exportCount; ++i) this.exportMap[i] = new AsyncLoading2_1.FExportMapEntry(Ar); this.exportsLazy = new Array(exportCount); // Export bundle entries Ar.pos = summary.exportBundleEntriesOffset; const exportBundleEntriesLen = exportCount * 2; this.exportBundleEntries = new Array(exportBundleEntriesLen); for (let i = 0; i < exportBundleEntriesLen; ++i) this.exportBundleEntries[i] = new AsyncLoading2_1.FExportBundleEntry(Ar); // Export bundle headers Ar.pos = summary.graphDataOffset; const exportBundleHeadersLen = storeEntry.exportBundleCount; this.exportBundleHeaders = new Array(exportBundleHeadersLen); for (let i = 0; i < exportBundleHeadersLen; ++i) this.exportBundleHeaders[i] = new AsyncLoading2_1.FExportBundleHeader(Ar); allExportDataOffset = summary.headerSize; } else { const summary = new AsyncLoading2_1.FPackageSummary(Ar); // Name map this.nameMap = new FNameMap_1.FNameMap(); if (summary.nameMapNamesSize > 0) { const nameMapNamesData = new FByteArchive_1.FByteArchive(uasset.subarray(0, summary.nameMapNamesOffset + summary.nameMapNamesSize)); nameMapNamesData.pos = summary.nameMapNamesOffset; const nameMapHashesData = new FByteArchive_1.FByteArchive(uasset.subarray(0, summary.nameMapHashesOffset + summary.nameMapHashesSize)); nameMapHashesData.pos = summary.nameMapHashesOffset; this.nameMap.load(nameMapNamesData, nameMapHashesData, AsyncLoading2_1.FMappedName_EType.Package); } const diskPackageName = this.nameMap.getName(summary.name); this.fileName = diskPackageName.text; this.packageFlags = summary.packageFlags; this.name = this.fileName; // Import map Ar.pos = summary.importMapOffset; const importCount = (summary.exportMapOffset - summary.importMapOffset) / 8; this.importMap = new Array(importCount); for (let i = 0; i < importCount; ++i) this.importMap[i] = new AsyncLoading2_1.FPackageObjectIndex(Ar); // Export map Ar.pos = summary.exportMapOffset; const exportCount = storeEntry.exportCount; //(summary.exportBundlesOffset - summary.exportMapOffset) / FExportMapEntry.SIZE this.exportMap = new Array(exportCount); for (let i = 0; i < exportCount; ++i) this.exportMap[i] = new AsyncLoading2_1.FExportMapEntry(Ar); this.exportsLazy = new Array(exportCount); // Export bundles Ar.pos = summary.exportBundlesOffset; const exportBundleHeadersLen = storeEntry.exportBundleCount; this.exportBundleHeaders = new Array(exportBundleHeadersLen); for (let i = 0; i < exportBundleHeadersLen; ++i) this.exportBundleHeaders[i] = new AsyncLoading2_1.FExportBundleHeader(Ar); const exportBundleEntriesLen = exportCount * 2; this.exportBundleEntries = new Array(exportBundleEntriesLen); for (let i = 0; i < exportBundleEntriesLen; ++i) this.exportBundleEntries[i] = new AsyncLoading2_1.FExportBundleEntry(Ar); allExportDataOffset = summary.graphDataOffset + summary.graphDataSize; } // Preload dependencies const importedPackageIds = storeEntry.importedPackages; this.importedPackages = new Lazy_1.Lazy(() => importedPackageIds.map(it => provider.loadGameFile(it))); // Populate lazy exports let currentExportDataOffset = allExportDataOffset; for (const exportBundle of this.exportBundleHeaders) { for (let i = 0; i < exportBundle.entryCount; ++i) { const entry = this.exportBundleEntries[exportBundle.firstEntryIndex + i]; if (entry.commandType === AsyncLoading2_1.EExportCommandType.ExportCommandType_Serialize) { const localExportIndex = entry.localExportIndex; const exp = this.exportMap[localExportIndex]; const localExportDataOffset = currentExportDataOffset; this.exportsLazy[localExportIndex] = new Lazy_1.Lazy(() => { // Create const objectName = this.nameMap.getName(exp.objectName); const obj = Package_1.Package.constructExport(this.resolveObjectIndex(exp.classIndex)?.getObject()?.value); obj.name = objectName.text; const objOuter = this.resolveObjectIndex(exp.outerIndex); obj.outer = (objOuter instanceof ResolvedExportObject ? objOuter?.exportObject?.value : null) || this; const objTemplate = this.resolveObjectIndex(exp.templateIndex); obj.template = objTemplate instanceof ResolvedExportObject ? objTemplate?.exportObject : null; obj.flags = Math.floor(exp.objectFlags); // Serialize const Ar = new FExportArchive_1.FExportArchive(uasset, obj, this); Ar.useUnversionedPropertySerialization = (this.packageFlags & EPackageFlags_1.EPackageFlags.PKG_UnversionedProperties) !== 0; Ar.uassetSize = exp.cookedSerialOffset - localExportDataOffset; Ar.bulkDataStartOffset = this.bulkDataStartOffset; Ar.pos = localExportDataOffset; const validPos = Ar.pos + exp.cookedSerialSize; try { obj.deserialize(Ar, validPos); if (validPos !== Ar.pos) { console.warn(`Did not read ${obj.exportType} correctly, ${validPos - Ar.pos} bytes remaining (${obj.getPathName0()})`); } } catch (e) { if (e instanceof Exceptions_1.MissingSchemaException && !Config_1.Config.GSuppressMissingSchemaErrors) { console.warn(e); } else { throw e; } } return obj; }); currentExportDataOffset += exp.cookedSerialSize; } } } this.bulkDataStartOffset = currentExportDataOffset; } /** * Resolves an object index * @param {FPackageObjectIndex} index Object index to resolve * @param {boolean} throwIfNotFound (default: true) Whether to throw an error if it wasn't found * @returns {ResolvedExportObject | ResolvedScriptObject | null} Object * @public */ resolveObjectIndex(index, throwIfNotFound = true) { if (index == null || index.isNull()) return null; if (index.isExport()) { return new ResolvedExportObject(index.toExport().toInt(), this); } else if (index.isScriptImport()) { const ent = this.globalPackageStore.scriptObjectEntries.get(index); return ent ? new ResolvedScriptObject(ent, this) : null; } else if (index.isPackageImport()) { const localProvider = this.provider; if (localProvider != null) { const localImportedPublicExportHashes = this.importedPublicExportHashes; if (localImportedPublicExportHashes != null) { const packageImportRef = index.toPackageImportRef(); const pkg = this.importedPackages.value[packageImportRef.importedPackageIndex]; if (pkg != null) { const pkgExpLen = pkg.exportMap.length; for (let exportIndex = 0; exportIndex < pkgExpLen; ++exportIndex) { const exportMapEntry = pkg.exportMap[exportIndex]; if (exportMapEntry.publicExportHash === localImportedPublicExportHashes[packageImportRef.importedPublicExportHashIndex]) { return new ResolvedExportObject(exportIndex, pkg); } } } } else { for (const pkg of this.importedPackages.value) { for (const exportIndex in pkg?.exportMap) { const exportMapEntry = pkg?.exportMap[exportIndex]; if (exportMapEntry.globalImportIndex?.equals(index)) { return new ResolvedExportObject(parseInt(exportIndex), pkg); } } } } } } if (throwIfNotFound) { throw new Exceptions_1.ParserException(sprintf_js_1.sprintf("Missing %s import 0x%016X for package %s", index.isScriptImport() ? "script" : "package", index.value(), this.fileName)); } return null; } /** * Finds an object by FPackageIndex * @param {FPackageIndex} index Index to look for * @returns {?any} Object or null * @public */ findObject(index) { if (index == null || index.isNull()) { return null; } else if (index.isExport()) { return this.exportsLazy[index.toExport()]; } else { const imp = this.importMap[index.toImport()]; return (imp ? this.resolveObjectIndex(imp, false)?.getObject() : null); } } /** * Finds an object by name * @param {string} objectName Name of object * @param {?string} className Class name of object * @returns {?Lazy<UObject>} Object or null * @public */ findObjectByName(objectName, className) { objectName = objectName.toLowerCase(); className = className?.toLowerCase(); let exportIndex = -1; for (let k = 0; k < this.exportMap.length; ++k) { const it = this.exportMap[k]; const name = this.nameMap.getName(it.objectName); const obj = name.text.toLowerCase() === objectName && (className == null || this.resolveObjectIndex(it.classIndex, false)?.name?.text?.toLowerCase() === className); if (obj) exportIndex = k; } return exportIndex !== -1 ? this.exportsLazy[exportIndex] : null; } /** * Turns this package into json * @param {?Locres} locres Locres to use * @returns {Array<IJson>} Json data * @public */ toJson(locres) { const object = []; for (const it of this.exports) { if (!(it instanceof UObject_1.UObject)) continue; const json = it.toJson(locres); object.push({ type: it.exportType, name: it.name, properties: json }); } return object; } /** * Finds an object minimal * @param {?FPackageIndex} index Index to look for * @returns {ResolvedExportObject | ResolvedScriptObject} Object * @public */ findObjectMinimal(index) { if (index == null || index.isNull()) { return null; } else if (index.isExport()) { return new ResolvedExportObject(index.toExport(), this); } else { const imp = this.importMap[index.toImport()]; return this.resolveObjectIndex(imp, false); } } } exports.IoPackage = IoPackage; class FImportedPackage { constructor(Ar) { this.importedPackageId = Ar.readUInt64(); const len = Ar.readInt32(); this.externalArcs = new Array(len); for (let i = 0; i < len; ++i) { this.externalArcs[i] = new FArc(Ar); } } } exports.FImportedPackage = FImportedPackage; class FArc { constructor(Ar) { this.fromExportBundleIndex = Ar.readInt32(); this.toExportBundleIndex = Ar.readInt32(); } } exports.FArc = FArc; class ResolvedExportObject extends Package_1.ResolvedObject { constructor(exportIndex, pkg) { super(pkg); this.exportIndex = exportIndex; this.exportMapEntry = pkg.exportMap[exportIndex]; this.exportObject = pkg.exportsLazy[exportIndex]; } get name() { return this.pkg.nameMap.getName(this.exportMapEntry.objectName); } getOuter() { return this.pkg.resolveObjectIndex(this.exportMapEntry.outerIndex) || new Package_1.ResolvedLoadedObject(this.pkg); } getClazz() { return this.pkg.resolveObjectIndex(this.exportMapEntry.classIndex); } getSuper() { return this.pkg.resolveObjectIndex(this.exportMapEntry.superIndex); } getObject() { return this.exportObject; } } class ResolvedScriptObject extends Package_1.ResolvedObject { constructor(scriptImport, pkg) { super(pkg); this.scriptImport = scriptImport; } get name() { return this.scriptImport.objectName.toName(); } getOuter() { return this.pkg.resolveObjectIndex(this.scriptImport.outerIndex); } getObject() { return new Lazy_1.Lazy(() => { const name = this.name; const struct = this.pkg.provider?.mappingsProvider?.getStruct(name); if (struct != null) { return struct; } else { const enumValues = this.pkg.provider?.mappingsProvider?.getEnum(name); if (enumValues != null) { const enm = new UEnum_1.UEnum(); enm.name = name.text; enm.names = new Array(enumValues.length); for (let i = 0; i < enumValues.length; ++i) { enm.names[i] = new Pair_1.Pair(FName_1.FName.dummy(`${name}::${enumValues[i]}`), i); } return enm; } else return null; } }); } }