Skip to content

Instantly share code, notes, and snippets.

@McSimp
Last active August 20, 2021 01:25
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save McSimp/ebe52164c10a96fba9247c3843499317 to your computer and use it in GitHub Desktop.
Save McSimp/ebe52164c10a96fba9247c3843499317 to your computer and use it in GitHub Desktop.
Test script to read data from Unreal catalog assets
const fs = require('fs');
class DataReader {
constructor(data, context) {
this.data = data;
this.context = context;
this.offset = 0;
}
readInt32LE() {
const result = this.data.readInt32LE(this.offset);
this.offset += 4;
return result;
}
readUInt32LE() {
const result = this.data.readUInt32LE(this.offset);
this.offset += 4;
return result;
}
readUInt16LE() {
const result = this.data.readUInt16LE(this.offset);
this.offset += 2;
return result;
}
readInt64LE() {
const result = this.data.readIntLE(this.offset, 6);
this.offset += 8;
return result;
}
readData(bytes) {
const result = this.data.slice(this.offset, this.offset + bytes);
this.offset += bytes;
return result;
}
readString(length) {
let result = this.data.toString('utf8', this.offset, this.offset + length);
this.offset += length;
if (length > 0) {
result = result.slice(0, -1);
}
return result;
}
readWString(length) {
let result = this.data.toString('utf16le', this.offset, this.offset + length * 2);
this.offset += length * 2;
if (length > 0) {
result = result.slice(0, -1);
}
return result;
}
readBool() {
const result = this.data.readInt8(this.offset);
this.offset += 1;
return result === 1 ? true : false;
}
readFloatLE() {
const result = this.data.readFloatLE(this.offset);
this.offset += 4;
return result;
}
seek(pos) {
this.offset = pos;
}
skip(length) {
this.offset += length;
}
tell() {
return this.offset;
}
}
class FGuid {
constructor(reader) {
this.A = reader.readUInt32LE();
this.B = reader.readUInt32LE();
this.C = reader.readUInt32LE();
this.D = reader.readUInt32LE();
}
}
class FCustomVersion {
constructor(reader) {
this.Key = new FGuid(reader);
this.Version = reader.readInt32LE();
}
}
class FCustomVersionSet {
constructor(reader) {
this.NumElements = reader.readInt32LE();
this.Elements = [];
for (let i = 0; i < this.NumElements; i++) {
this.Elements.push(new FCustomVersion(reader));
}
}
}
class FCustomVersionContainer {
constructor(reader) {
this.Versions = new FCustomVersionSet(reader);
}
}
class FString {
constructor(reader) {
this.SaveNum = reader.readInt32LE();
this.Data = reader.readString(this.SaveNum);
}
}
class FGenerationInfo {
constructor(reader) {
this.ExportCount = reader.readInt32LE();
this.NameCount = reader.readInt32LE();
}
}
class FEngineVersion {
constructor(reader) {
this.Major = reader.readUInt16LE();
this.Minor = reader.readUInt16LE();
this.Patch = reader.readUInt16LE();
this.Changelist = reader.readUInt32LE();
this.Branch = new FString(reader);
}
}
class TArray {
constructor(type, reader) {
this.NewNum = reader.readUInt32LE();
this.Elements = [];
for (let i = 0; i < this.NewNum; i++) {
this.Elements.push(new type(reader));
}
}
}
class FCompressedChunk {
constructor(reader) {
this.UncompressedOffset = reader.readInt32LE();
this.UncompressedSize = reader.readInt32LE();
this.CompressedOffset = reader.readInt32LE();
this.CompressedSize = reader.readInt32LE();
}
}
class FPackageFileSummary {
constructor(reader) {
this.Tag = reader.readInt32LE();
this.LegacyFileVersion = reader.readInt32LE();
this.LegacyUE3Version = reader.readInt32LE();
this.FileVersionUE4 = reader.readInt32LE();
this.FileVersionLicenseeUE4 = reader.readInt32LE();
this.CustomVersionContainer = new FCustomVersionContainer(reader);
this.TotalHeaderSize = reader.readInt32LE();
this.FolderName = new FString(reader);
this.PackageFlags = reader.readUInt32LE();
this.NameCount = reader.readInt32LE();
this.NameOffset = reader.readInt32LE();
this.GatherableTextDataCount = reader.readInt32LE();
this.GatherableTextDataOffset = reader.readInt32LE();
this.ExportCount = reader.readInt32LE();
this.ExportOffset = reader.readInt32LE();
this.ImportCount = reader.readInt32LE();
this.ImportOffset = reader.readInt32LE();
this.DependsOffset = reader.readInt32LE();
this.StringAssetReferencesCount = reader.readInt32LE();
this.StringAssetReferencesOffset = reader.readInt32LE();
this.SearchableNamesOffset = reader.readInt32LE();
this.ThumbnailTableOffset = reader.readInt32LE();
this.Guid = new FGuid(reader);
this.GenerationCount = reader.readInt32LE();
this.Generations = [];
for (let i = 0; i < this.GenerationCount; i++) {
this.Generations.push(new FGenerationInfo(reader));
}
this.SavedByEngineVersion = new FEngineVersion(reader);
this.CompatibleWithEngineVersion = new FEngineVersion(reader);
this.CompressionFlags = reader.readUInt32LE();
this.CompressedChunks = new TArray(FCompressedChunk, reader);
this.PackageSource = reader.readUInt32LE();
this.AdditionalPackagesToCook = new TArray(FString, reader);
this.AssetRegistryDataOffset = reader.readInt32LE();
this.BulkDataStartOffset = reader.readInt32LE();
this.WorldTileInfoDataOffset = reader.readInt32LE();
this.ChunkIDs = new TArray(Number, reader);
this.PreloadDependencyCount = reader.readInt32LE();
this.PreloadDependencyOffset = reader.readInt32LE();
}
}
class FNameEntrySerialized
{
constructor(reader) {
this.StringLen = reader.readInt32LE();
if (this.StringLen < 0) {
this.StringLen = -this.StringLen;
this.Str = reader.readWString(this.StringLen);
} else {
this.Str = reader.readString(this.StringLen);
}
this.NonCasePreservingHash = reader.readUInt16LE();
this.CasePreservingHash = reader.readUInt16LE();
}
}
class FName {
constructor(reader) {
this.NameIndex = reader.readInt32LE();
this.Number = reader.readInt32LE();
this._nameMap = reader.context.NameMap;
}
toString() {
return this._nameMap[this.NameIndex].Str;
}
}
class FPackageIndex {
constructor(reader) {
this.Index = reader.readInt32LE();
this._importMap = reader.context.ImportMap;
}
isImport() {
return this.Index < 0;
}
isExport() {
return this.Index > 0;
}
isNull() {
return this.Index == 0;
}
toImport() {
return -this.Index - 1;
}
toExport() {
return this.Index - 1;
}
get Package() {
if (this.isImport()) {
return this._importMap[this.toImport()];
} else if (this.isExport()) {
return this._importMap[this.toExport()];
}
}
}
class FObjectImport {
constructor(reader) {
this.ClassPackage = new FName(reader);
this.ClassName = new FName(reader);
this.OuterIndex = new FPackageIndex(reader);
this.ObjectName = new FName(reader);
}
toString() {
return this.ClassName.toString() + " " + this.ObjectName.toString();
}
}
class FObjectExport {
constructor(reader) {
this.ClassIndex = new FPackageIndex(reader);
this.SuperIndex = new FPackageIndex(reader);
this.TemplateIndex = new FPackageIndex(reader);
this.OuterIndex = new FPackageIndex(reader);
this.ObjectName = new FName(reader);
this.Save = reader.readUInt32LE();
this.SerialSize = reader.readInt64LE();
this.SerialOffset = reader.readInt64LE();
this.bForcedExport = reader.readBool();
this.bNotForClient = reader.readBool();
this.bNotForServer = reader.readBool();
this.PackageGuid = new FGuid(reader);
this.PackageFlags = reader.readUInt32LE();
}
toString() {
return this.ClassIndex.Package.ObjectName.toString() + " " + this.ObjectName.toString();
}
}
const uassetData = fs.readFileSync('UmodelSaved/DA_Featured_CID_065_Athena_Commando_F_SkiGirl_FRA.uasset');
const asset = {};
const reader = new DataReader(uassetData, asset);
asset.Summary = new FPackageFileSummary(reader);
reader.seek(asset.Summary.NameOffset);
asset.NameMap = [];
for (let i = 0; i < asset.Summary.NameCount; i++) {
asset.NameMap.push(new FNameEntrySerialized(reader));
}
reader.seek(asset.Summary.ImportOffset);
asset.ImportMap = [];
for (let i = 0; i < asset.Summary.ImportCount; i++) {
asset.ImportMap.push(new FObjectImport(reader));
}
reader.seek(asset.Summary.ExportOffset);
asset.ExportMap = [];
for (let i = 0; i < asset.Summary.ExportCount; i++) {
asset.ExportMap.push(new FObjectExport(reader));
}
console.log("Exports:");
for (let i = 0; i < asset.Summary.ExportCount; i++) {
console.log(asset.ExportMap[i].toString());
}
console.log("========");
const uexpData = fs.readFileSync('UmodelSaved/DA_Featured_CID_065_Athena_Commando_F_SkiGirl_FRA.uexp');
const expReader = new DataReader(uexpData, asset);
class FPropertyTag {
constructor(reader) {
this.Name = new FName(reader);
if (this.Name.toString() == "None") {
return;
}
this.Type = new FName(reader);
this.Size = reader.readInt32LE();
this.ArrayIndex = reader.readInt32LE();
const typeStr = this.Type.toString();
if (typeStr == "StructProperty") {
this.StructName = new FName(reader);
this.StructGuid = new FGuid(reader);
} else if (typeStr == "BoolProperty") {
this.BoolVal = reader.readBool();
} else if (typeStr == "ByteProperty") {
this.EnumName = new FName(reader);
} else if (typeStr == "ArrayProperty") {
this.InnerType = new FName(reader);
}
this.HasPropertyGuid = reader.readBool();
if (this.HasPropertyGuid) {
this.PropertyGuid = new FGuid(reader);
}
}
toString() {
let str = this.Type.toString() + " ";
if (this.Type.toString() == "StructProperty") {
str += this.StructName.toString() + " ";
}
return str + this.Name.toString() + " (Size = " + this.Size + ")";
}
}
class FVector2D {
constructor(reader) {
this.x = reader.readFloatLE();
this.y = reader.readFloatLE();
}
}
class FLinearColor {
constructor(reader) {
this.R = reader.readFloatLE();
this.G = reader.readFloatLE();
this.B = reader.readFloatLE();
this.A = reader.readFloatLE();
}
}
const typeSerializers = {
"Vector2D": (reader) => { return new FVector2D(reader); },
"LinearColor": (reader) => { return new FLinearColor(reader); }
}
function UScriptStruct_SerializeItem(reader, tag, obj, key) {
const typeStr = tag.StructName.toString();
if (typeStr in typeSerializers) {
obj[key] = typeSerializers[typeStr](reader);
return;
}
obj[key] = {};
SerializeTaggedProperties(reader, obj[key]);
}
function SerializeTaggedProperty(reader, tag, obj) {
const typeStr = tag.Type.toString();
if (typeStr == "BoolProperty") {
obj[tag.Name.toString()] = tag.BoolVal;
} else if (typeStr == "StructProperty") {
UScriptStruct_SerializeItem(reader, tag, obj, tag.Name.toString());
} else if (typeStr == "ObjectProperty") {
obj[tag.Name.toString()] = new FPackageIndex(reader);
} else {
console.log("unhandled property:", typeStr);
}
}
function SerializeTaggedProperties(reader, obj) {
while (true) {
const tag = new FPropertyTag(reader);
if (tag.Name.toString() == "None") {
break;
}
const pos = reader.tell();
//console.log("<",tag.toString(),">");
SerializeTaggedProperty(reader, tag, obj);
//console.log("</",tag.toString(),">");
if (reader.tell() != (pos + tag.Size)) {
console.log("failed to parse it all", reader.tell(), pos + tag.Size);
reader.seek(pos + tag.Size);
}
}
}
class UObject {
constructor(reader) {
SerializeTaggedProperties(reader, this);
}
}
const test = new UObject(expReader);
console.log("TileImage:");
console.log(" ImageSize = " + test.TileImage.ImageSize.x + " x " + test.TileImage.ImageSize.y);
console.log(" ResourceObject = " + test.TileImage.ResourceObject.Package.toString());
console.log("DetailsImage:");
console.log(" ImageSize = " + test.DetailsImage.ImageSize.x + " x " + test.DetailsImage.ImageSize.y);
console.log(" ResourceObject = " + test.DetailsImage.ResourceObject.Package.toString());
console.log("Gradient:");
console.log(" Start = (" + test.Gradient.Start.R + ", " + test.Gradient.Start.G + ", " + test.Gradient.Start.B + ", " + test.Gradient.Start.A + ")");
console.log(" Stop = (" + test.Gradient.Stop.R + ", " + test.Gradient.Stop.G + ", " + test.Gradient.Stop.B + ", " + test.Gradient.Stop.A + ")");
console.log("Background:");
console.log(" Color = (" + test.Background.R + ", " + test.Background.G + ", " + test.Background.B + ", " + test.Background.A + ")");
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment