Last active
August 20, 2021 01:25
-
-
Save McSimp/ebe52164c10a96fba9247c3843499317 to your computer and use it in GitHub Desktop.
Test script to read data from Unreal catalog assets
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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