unreal.js
Version:
A pak reader for games like VALORANT & Fortnite written in Node.JS
397 lines (396 loc) • 18.8 kB
JavaScript
"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;
}
});
}
}