Exemple #1
0
        /// <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;
                    }
                }
            }
        }