/// <summary>S /// UDKPackage class constructor. It also load namelist, importlist and exportinfo (not exportdata) from udk file /// </summary> /// <param name="filePath">full path + file name of desired udk file.</param> private UDKPackage(Stream fs, string filePath) : base(filePath != null ? Path.GetFullPath(filePath) : null) { #region Header uint magic = fs.ReadUInt32(); if (magic != packageTagLittleEndian) { throw new FormatException("Not an Unreal package!"); } ushort unrealVersion = fs.ReadUInt16(); ushort licenseeVersion = fs.ReadUInt16(); FullHeaderSize = fs.ReadInt32(); int foldernameStrLen = fs.ReadInt32(); //always "None", so don't bother saving result if (foldernameStrLen > 0) { folderName = fs.ReadStringASCIINull(foldernameStrLen); } else { folderName = fs.ReadStringUnicodeNull(foldernameStrLen * -2); } Flags = (EPackageFlags)fs.ReadUInt32(); //if (Flags.HasFlag(EPackageFlags.Compressed)) //{ // throw new FormatException("Cannot read compressed UDK packages!"); //} NameCount = fs.ReadInt32(); NameOffset = fs.ReadInt32(); ExportCount = fs.ReadInt32(); ExportOffset = fs.ReadInt32(); ImportCount = fs.ReadInt32(); ImportOffset = fs.ReadInt32(); DependencyTableOffset = fs.ReadInt32(); importExportGuidsOffset = fs.ReadInt32(); importGuidsCount = fs.ReadInt32(); exportGuidsCount = fs.ReadInt32(); thumbnailTableOffset = fs.ReadInt32(); PackageGuid = fs.ReadGuid(); uint generationsTableCount = fs.ReadUInt32(); if (generationsTableCount > 0) { generationsTableCount--; Gen0ExportCount = fs.ReadInt32(); Gen0NameCount = fs.ReadInt32(); Gen0NetworkedObjectCount = fs.ReadInt32(); } //don't care about other gens, so skip them fs.Skip(generationsTableCount * 12); engineVersion = fs.ReadInt32(); cookedContentVersion = fs.ReadInt32(); //skip compression type chunks. Decompressor will handle that long compressionInfoOffset = fs.Position; fs.SkipInt32(); int numChunks = fs.ReadInt32(); fs.Skip(numChunks * 16); packageSource = fs.ReadUInt32(); //additional packages to cook, and texture allocation, but we don't care about those, so we won't read them in. #endregion Stream inStream = fs; if (IsCompressed && numChunks > 0) { inStream = CompressionHelper.DecompressPackage(new EndianReader(fs), compressionInfoOffset); } var reader = new EndianReader(inStream); //these will always be little endian so we don't actually use this except for passing //through to methods that can use endianness inStream.JumpTo(NameOffset); for (int i = 0; i < NameCount; i++) { var name = inStream.ReadUnrealString(); names.Add(name); nameLookupTable[name] = i; inStream.Skip(8); } inStream.JumpTo(ImportOffset); for (int i = 0; i < ImportCount; i++) { ImportEntry imp = new ImportEntry(this, reader) { Index = i }; imp.PropertyChanged += importChanged; imports.Add(imp); } //read exportTable (ExportEntry constructor reads export data) inStream.JumpTo(ExportOffset); for (int i = 0; i < ExportCount; i++) { ExportEntry e = new ExportEntry(this, reader) { Index = i }; e.PropertyChanged += exportChanged; exports.Add(e); } }
/// <summary> /// Compares this package to another IMEPackage. The calling package must implement IMEPackage or this will throw an exception. /// </summary> /// <param name="compareFile"></param> /// <returns></returns> public List <EntryStringPair> CompareToPackage(IMEPackage compareFile) { if (this is IMEPackage thisPackage) { if (thisPackage.Game != compareFile.Game) { throw new Exception("Can't compare files, they're for different games!"); } if (thisPackage.Platform != compareFile.Platform) { throw new Exception("Cannot compare packages across platforms!"); } var changedImports = new List <EntryStringPair>(); var changedNames = new List <EntryStringPair>(); var changedExports = new List <EntryStringPair>(); #region Exports Comparison { // these brackets are here to scope the variables so same named ones can be used in a later chunk int numExportsToEnumerate = Math.Min(ExportCount, compareFile.ExportCount); for (int i = 0; i < numExportsToEnumerate; i++) { ExportEntry exp1 = Exports[i]; ExportEntry exp2 = compareFile.Exports[i]; //make data offset and data size the same, as the exports could be the same even if it was appended later. //The datasize being different is a data difference not a true header difference so we won't list it here. byte[] header1 = exp1.Header.TypedClone(); byte[] header2 = exp2.Header.TypedClone(); Buffer.BlockCopy(BitConverter.GetBytes((long)0), 0, header1, 32, sizeof(long)); Buffer.BlockCopy(BitConverter.GetBytes((long)0), 0, header2, 32, sizeof(long)); if (!header1.SequenceEqual(header2)) { changedExports.Add(new EntryStringPair(exp1, $"Export header has changed: {exp1.UIndex} {exp1.InstancedFullPath} ({exp1.ClassName})")); } if (!exp1.Data.SequenceEqual(exp2.Data)) { changedExports.Add(new EntryStringPair(exp1, $"Export data has changed: {exp1.UIndex} {exp1.InstancedFullPath} ({exp1.ClassName})")); } } IMEPackage enumerateExtras = thisPackage; string file = "this file"; if (compareFile.ExportCount > numExportsToEnumerate) { file = "other file"; enumerateExtras = compareFile; } for (int i = numExportsToEnumerate; i < enumerateExtras.ExportCount; i++) { Debug.WriteLine( $"Export only exists in {file}: {i + 1} {enumerateExtras.Exports[i].InstancedFullPath}"); changedExports.Add(new EntryStringPair( enumerateExtras.Exports[i].FileRef == this ? enumerateExtras.Exports[i] : null, $"Export only exists in {file}: {i + 1} {enumerateExtras.Exports[i].InstancedFullPath}")); } } #endregion #region Imports Comparison { int numImportsToEnumerate = Math.Min(ImportCount, compareFile.ImportCount); for (int i = 0; i < numImportsToEnumerate; i++) { ImportEntry imp1 = Imports[i]; ImportEntry imp2 = compareFile.Imports[i]; if (!imp1.Header.SequenceEqual(imp2.Header)) { changedImports.Add(new EntryStringPair(imp1, $"Import header has changed: {imp1.UIndex} {imp1.InstancedFullPath}")); } } IMEPackage enumerateExtras = thisPackage; string file = "this file"; if (compareFile.ImportCount > numImportsToEnumerate) { file = "other file"; enumerateExtras = compareFile; } for (int i = numImportsToEnumerate; i < enumerateExtras.ImportCount; i++) { Debug.WriteLine( $"Import only exists in {file}: {-i - 1} {enumerateExtras.Imports[i].InstancedFullPath}"); changedImports.Add(new EntryStringPair( enumerateExtras.Imports[i].FileRef == this ? enumerateExtras.Imports[i] : null, $"Import only exists in {file}: {-i - 1} {enumerateExtras.Imports[i].InstancedFullPath}")); } } #endregion #region Names Comparison { int numNamesToEnumerate = Math.Min(NameCount, compareFile.NameCount); for (int i = 0; i < numNamesToEnumerate; i++) { var name1 = Names[i]; var name2 = compareFile.Names[i]; //if (!StructuralComparisons.StructuralEqualityComparer.Equals(header1, header2)) if (!name1.Equals(name2, StringComparison.InvariantCultureIgnoreCase)) { changedNames.Add(new EntryStringPair(null, $"Name {i} is different: {name1} |vs| {name2}")); } } IMEPackage enumerateExtras = thisPackage; string file = "this file"; if (compareFile.NameCount > numNamesToEnumerate) { file = "other file"; enumerateExtras = compareFile; } for (int i = numNamesToEnumerate; i < enumerateExtras.NameCount; i++) { Debug.WriteLine($"Name only exists in {file}: {i} {enumerateExtras.Names[i]}"); changedNames.Add(new EntryStringPair(null, $"Name only exists in {file}: {i} {enumerateExtras.Names[i]}")); } } #endregion var fullList = new List <EntryStringPair>(); fullList.AddRange(changedExports); fullList.AddRange(changedImports); fullList.AddRange(changedNames); return(fullList); } throw new Exception("Source object must implement IMEPackage to support package comparisons"); }
public static T GetProperty <T>(this ExportEntry export, string name) where T : Property { return(export.GetProperties().GetProp <T>(name)); }
public void RemoveTrailingTrash() { ExportEntry trashPackage = exports.FirstOrDefault(exp => exp.ObjectName == TrashPackageName); if (trashPackage == null) { return; } int trashPackageUIndex = trashPackage.UIndex; //make sure the first trashed export is the trashpackage foreach (ExportEntry exp in exports) { if (exp == trashPackage) { //trashpackage is the first trashed export, so we're good break; } if (exp.idxLink == trashPackageUIndex) { //turn this into trashpackage, turn old trashpackage into regular Trash, and point all trash entries to the new trashpackage exp.ObjectName = TrashPackageName; exp.idxLink = 0; exp.PackageGUID = TrashPackageGuid; trashPackage.ObjectName = "Trash"; trashPackage.idxLink = exp.UIndex; trashPackage.PackageGUID = Guid.Empty; foreach (IEntry entry in trashPackage.GetChildren()) { entry.idxLink = exp.UIndex; } trashPackage = exp; trashPackageUIndex = trashPackage.UIndex; break; } } //remove imports for (int i = ImportCount - 1; i >= 0; i--) { ImportEntry lastImport = imports[i]; if (lastImport.idxLink != trashPackageUIndex) { //non-trash import, so stop removing break; } lastImport.PropertyChanged -= importChanged; imports.RemoveAt(i); updateTools(PackageChange.ImportRemove, lastImport.UIndex); } if (ImportCount != imports.Count) { ImportCount = imports.Count; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ImportCount))); } //remove exports for (int i = ExportCount - 1; i >= 0; i--) { ExportEntry lastExport = exports[i]; if (lastExport.idxLink != trashPackageUIndex) { //non-trash export, so stop removing break; } lastExport.PropertyChanged -= importChanged; exports.RemoveAt(i); updateTools(PackageChange.ExportRemove, lastExport.UIndex); } if (ExportCount != exports.Count) { ExportCount = exports.Count; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ExportCount))); } //if there are no more trashed imports or exports, and if the TrashPackage is the last export, remove it if (exports.LastOrDefault() is ExportEntry finalExport && finalExport == trashPackage && trashPackage.GetChildren().IsEmpty()) { trashPackage.PropertyChanged -= importChanged; exports.Remove(trashPackage); updateTools(PackageChange.ExportRemove, trashPackage.UIndex); } if (ExportCount != exports.Count) { ExportCount = exports.Count; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ExportCount))); } }
public static HashSet <int> GetReferencedEntries(this IMEPackage pcc, bool getreferenced = true, bool getactorrefs = false, ExportEntry startatexport = null) { var result = new HashSet <int>(); Level level = null; Stack <IEntry> entriesToEvaluate = new Stack <IEntry>(); HashSet <IEntry> entriesEvaluated = new HashSet <IEntry>(); HashSet <IEntry> entriesReferenced = new HashSet <IEntry>(); if (startatexport != null) //Start at object { entriesToEvaluate.Push(startatexport); entriesReferenced.Add(startatexport); entriesEvaluated.Add(pcc.GetUExport(startatexport.idxLink)); //Do not go up the chain if parsing an export } else if (pcc.Exports.FirstOrDefault(exp => exp.ClassName == "Level") is ExportEntry levelExport) //Evaluate level with only actors, model+components, sequences and level class being processed. { level = ObjectBinary.From <Level>(levelExport); entriesEvaluated.Add(null); //null stops future evaluations entriesEvaluated.Add(levelExport); entriesReferenced.Add(levelExport); var levelclass = levelExport.Class; entriesToEvaluate.Push(levelclass); entriesReferenced.Add(levelclass); foreach (int actoridx in level.Actors) { var actor = pcc.GetEntry(actoridx); entriesToEvaluate.Push(actor); entriesReferenced.Add(actor); } var model = pcc.GetEntry(level.Model?.value ?? 0); entriesToEvaluate.Push(model); entriesReferenced.Add(model); foreach (var comp in level.ModelComponents) { var compxp = pcc.GetEntry(comp); entriesToEvaluate.Push(compxp); entriesReferenced.Add(compxp); } foreach (var seq in level.GameSequences) { var seqxp = pcc.GetEntry(seq); entriesToEvaluate.Push(seqxp); entriesReferenced.Add(seqxp); } var localpackage = pcc.Exports.FirstOrDefault(x => x.ClassName == "Package" && x.ObjectName.ToString().ToLower() == Path.GetFileNameWithoutExtension(pcc.FilePath).ToLower()); // Make sure world, localpackage, shadercache are all marked as referenced. entriesToEvaluate.Push(localpackage); entriesReferenced.Add(localpackage); var world = levelExport.Parent; entriesToEvaluate.Push(world); entriesReferenced.Add(world); var shadercache = pcc.Exports.FirstOrDefault(x => x.ClassName == "ShaderCache"); if (shadercache != null) { entriesEvaluated.Add(shadercache); entriesReferenced.Add(shadercache); entriesToEvaluate.Push(shadercache.Class); entriesReferenced.Add(shadercache.Class); } } else { return(result); //If this has no level it is a reference / seekfree package and shouldn't be compacted. } var theserefs = new HashSet <IEntry>(); while (!entriesToEvaluate.IsEmpty()) { var ent = entriesToEvaluate.Pop(); try { if (entriesEvaluated.Contains(ent) || (ent?.UIndex ?? 0) == 0 || (getactorrefs && !ent.InstancedFullPath.Contains("PersistentLevel"))) { continue; } entriesEvaluated.Add(ent); if (ent.idxLink != 0) { theserefs.Add(pcc.GetEntry(ent.idxLink)); } if (ent.UIndex < 0) { continue; } var exp = pcc.GetUExport(ent.UIndex); //find header references only if doing non-actors if (!getactorrefs) { if ((exp.Archetype?.UIndex ?? 0) != 0) { theserefs.Add(exp.Archetype); } if ((exp.Class?.UIndex ?? 0) != 0) { theserefs.Add(exp.Class); } if ((exp.SuperClass?.UIndex ?? 0) != 0) { theserefs.Add(exp.SuperClass); } if (exp.HasComponentMap) { foreach (var kvp in exp.ComponentMap) { //theserefs.Add(pcc.GetEntry(kvp.Value)); //THIS IS INCORRECT SHOULD NOT BE ON UINDEX } } } else { exp.CondenseArchetypes(); } //find property references findPropertyReferences(exp.GetProperties(), exp); //find binary references if (!exp.IsDefaultObject && ObjectBinary.From(exp) is ObjectBinary objBin) { List <(UIndex, string)> indices = objBin.GetUIndexes(exp.FileRef.Game); foreach ((UIndex uIndex, string propName) in indices) { if (uIndex != exp.UIndex) { theserefs.Add(pcc.GetEntry(uIndex)); } } } foreach (var reference in theserefs) { if (!entriesEvaluated.Contains(reference)) { entriesToEvaluate.Push(reference); entriesReferenced.Add(reference); } } theserefs.Clear(); } catch (Exception e) { Console.WriteLine($"Error getting references {ent.UIndex} {ent.ObjectName.Instanced}: {e.Message}"); } } if (getreferenced) { foreach (var entry in entriesReferenced) { result.Add(entry?.UIndex ?? 0); } } else { foreach (var xp in pcc.Exports) { if (!entriesReferenced.Contains(xp)) { result.Add(xp?.UIndex ?? 0); } } foreach (var im in pcc.Imports) { if (!entriesReferenced.Contains(im)) { result.Add(im?.UIndex ?? 0); } } } return(result); void findPropertyReferences(PropertyCollection props, ExportEntry exp) { foreach (Property prop in props) { switch (prop) { case ObjectProperty objectProperty: if (objectProperty.Value != 0 && objectProperty.Value != exp.UIndex) { theserefs.Add(pcc.GetEntry(objectProperty.Value)); } break; case DelegateProperty delegateProperty: if (delegateProperty.Value.Object != 0 && delegateProperty.Value.Object != exp.UIndex) { theserefs.Add(pcc.GetEntry(delegateProperty.Value.Object)); } break; case StructProperty structProperty: findPropertyReferences(structProperty.Properties, exp); break; case ArrayProperty <ObjectProperty> arrayProperty: for (int i = 0; i < arrayProperty.Count; i++) { ObjectProperty objProp = arrayProperty[i]; if (objProp.Value != 0 && objProp.Value != exp.UIndex) { theserefs.Add(pcc.GetEntry(objProp.Value)); } } break; case ArrayProperty <StructProperty> arrayProperty: for (int i = 0; i < arrayProperty.Count; i++) { StructProperty structProp = arrayProperty[i]; findPropertyReferences(structProp.Properties, exp); } break; } } } }