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