uasset/
lib.rs

1//! [![github]](https://github.com/jorgenpt/uasset-rs) [![crates-io]](https://crates.io/crates/uasset) [![docs-rs]](https://docs.rs/uasset)
2//!
3//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github
4//! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust
5//! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logoColor=white&logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiPjxwYXRoIGZpbGw9IiNmNWY1ZjUiIGQ9Ik00ODguNiAyNTAuMkwzOTIgMjE0VjEwNS41YzAtMTUtOS4zLTI4LjQtMjMuNC0zMy43bC0xMDAtMzcuNWMtOC4xLTMuMS0xNy4xLTMuMS0yNS4zIDBsLTEwMCAzNy41Yy0xNC4xIDUuMy0yMy40IDE4LjctMjMuNCAzMy43VjIxNGwtOTYuNiAzNi4yQzkuMyAyNTUuNSAwIDI2OC45IDAgMjgzLjlWMzk0YzAgMTMuNiA3LjcgMjYuMSAxOS45IDMyLjJsMTAwIDUwYzEwLjEgNS4xIDIyLjEgNS4xIDMyLjIgMGwxMDMuOS01MiAxMDMuOSA1MmMxMC4xIDUuMSAyMi4xIDUuMSAzMi4yIDBsMTAwLTUwYzEyLjItNi4xIDE5LjktMTguNiAxOS45LTMyLjJWMjgzLjljMC0xNS05LjMtMjguNC0yMy40LTMzLjd6TTM1OCAyMTQuOGwtODUgMzEuOXYtNjguMmw4NS0zN3Y3My4zek0xNTQgMTA0LjFsMTAyLTM4LjIgMTAyIDM4LjJ2LjZsLTEwMiA0MS40LTEwMi00MS40di0uNnptODQgMjkxLjFsLTg1IDQyLjV2LTc5LjFsODUtMzguOHY3NS40em0wLTExMmwtMTAyIDQxLjQtMTAyLTQxLjR2LS42bDEwMi0zOC4yIDEwMiAzOC4ydi42em0yNDAgMTEybC04NSA0Mi41di03OS4xbDg1LTM4Ljh2NzUuNHptMC0xMTJsLTEwMiA0MS40LTEwMi00MS40di0uNmwxMDItMzguMiAxMDIgMzguMnYuNnoiPjwvcGF0aD48L3N2Zz4K
6//!
7//! # The Rust uasset Library
8//!
9//! `uasset` is a pure Rust implementation of the Unreal Engine `.uasset` file format.
10//! It gives you direct access to fields & values in the uasset format, and is intended
11//! to allow you to build tools outside of the Unreal Editor to work with uassets.
12//!
13//! ## Usage
14//!
15//! To use `uasset`, first add this to your `Cargo.toml`:
16//!
17//! ```toml
18//! [dependencies]
19//! uasset = "^0.2"
20//! ```
21//!
22//! Then import [`AssetHeader`] into your program:
23//!
24//! ```rust
25//! use uasset::AssetHeader;
26//! ```
27//!
28//! Finally, parse a file using [`AssetHeader::new`].
29//!
30//! ## Example
31//!
32//! ```rust
33//! # use uasset::{AssetHeader, Result};
34//! # use std::{fs::File, path::PathBuf};
35//! # fn main() -> Result<()> {
36//! # let path = PathBuf::from("assets/UE410/SimpleRefs/SimpleRefsRoot.uasset");
37//! let file = File::open(path)?;
38//! let package = AssetHeader::new(&file)?;
39//! for import in package.package_import_iter() {
40//!     println!("Import: {}", import);
41//! }
42//! # Ok(())
43//! # }
44//! ```
45//!
46//! ## Crate features
47//!
48//! * `commandline-tool` -
49//!   Allows the building of a `uasset` command line tool that can be used to inspect specific assets.
50
51// BEGIN - Embark standard lints v0.3
52// do not change or add/remove here, but one can add exceptions after this section
53// for more info see: <https://github.com/EmbarkStudios/rust-ecosystem/issues/59>
54#![deny(unsafe_code)]
55#![warn(
56    clippy::all,
57    clippy::await_holding_lock,
58    clippy::dbg_macro,
59    clippy::debug_assert_with_mut_call,
60    clippy::doc_markdown,
61    clippy::empty_enum,
62    clippy::enum_glob_use,
63    clippy::exit,
64    clippy::explicit_into_iter_loop,
65    clippy::filter_map_next,
66    clippy::fn_params_excessive_bools,
67    clippy::if_let_mutex,
68    clippy::imprecise_flops,
69    clippy::inefficient_to_string,
70    clippy::large_types_passed_by_value,
71    clippy::let_unit_value,
72    clippy::linkedlist,
73    clippy::lossy_float_literal,
74    clippy::macro_use_imports,
75    clippy::map_err_ignore,
76    clippy::map_flatten,
77    clippy::map_unwrap_or,
78    clippy::match_on_vec_items,
79    clippy::match_same_arms,
80    clippy::match_wildcard_for_single_variants,
81    clippy::mem_forget,
82    clippy::mismatched_target_os,
83    clippy::needless_borrow,
84    clippy::needless_continue,
85    clippy::option_option,
86    clippy::ref_option_ref,
87    clippy::rest_pat_in_fully_bound_structs,
88    clippy::string_add_assign,
89    clippy::string_add,
90    clippy::string_to_string,
91    clippy::suboptimal_flops,
92    clippy::todo,
93    clippy::unimplemented,
94    clippy::unnested_or_patterns,
95    clippy::unused_self,
96    clippy::verbose_file_reads,
97    future_incompatible,
98    nonstandard_style,
99    rust_2018_idioms
100)]
101// END - Embark standard lints v0.3
102// crate-specific exceptions:
103#![allow(
104    clippy::let_unit_value, // This one is enabled because we use `let` with unit values to identify fields that aren't parsed.
105)]
106
107mod archive;
108pub mod enums;
109mod error;
110mod serialization;
111
112use archive::SerializedObjectVersion;
113use binread::BinReaderExt;
114use serialization::{
115    ArrayStreamInfo, Parseable, Skippable, StreamInfo, UnrealArray, UnrealArrayIterator,
116    UnrealClassImport, UnrealCompressedChunk, UnrealCustomVersion, UnrealEngineVersion,
117    UnrealGenerationInfo, UnrealGuid, UnrealGuidCustomVersion, UnrealNameEntryWithHash,
118    UnrealString, UnrealThumbnailInfo,
119};
120use std::{
121    borrow::Cow,
122    cmp::Ordering,
123    io::{Read, Seek, SeekFrom},
124    num::NonZeroU32,
125};
126
127pub use archive::{Archive, CustomVersionSerializationFormat};
128pub use enums::{ObjectVersion, ObjectVersionUE5, PackageFlags};
129pub use error::{Error, InvalidNameIndexError, Result};
130use crate::serialization::UnrealObjectExport;
131
132/// A reference to a name in the [`AssetHeader::names`] name table. You can use [`AssetHeader::resolve_name`] to get a human-readable
133/// string from a `NameReference`. It only makes sense to compare `NameReference`s from the same `AssetHeader`.
134#[derive(Clone, Copy, Debug, PartialEq)]
135pub struct NameReference {
136    /// The index in the name table
137    pub index: u32,
138    /// If present, one greater than an optional suffix on the name (`Some(1)` means the name should have `_0` appended to it).
139    /// The oddness with it being non-zero is based on how this is serialized. You should use
140    pub number: Option<NonZeroU32>,
141}
142
143/// A reference to either an import or an export in the asset.
144#[derive(Debug)]
145pub enum ObjectReference {
146    None,
147    Export { export_index: usize },
148    Import { import_index: usize },
149}
150
151impl From<i32> for ObjectReference {
152    fn from(index: i32) -> Self {
153        match index.cmp(&0) {
154            Ordering::Equal => ObjectReference::None,
155            Ordering::Greater => ObjectReference::Export {
156                export_index: (index - 1) as usize,
157            },
158            Ordering::Less => ObjectReference::Import {
159                import_index: -(index + 1) as usize,
160            },
161        }
162    }
163}
164
165/// A reference to an object in another package. Typically accessed through [`AssetHeader::package_import_iter`], but you can also
166/// manually resolve the [`NameReference`]s. (C++ name: `FObjectImport`)
167#[derive(Debug)]
168pub struct ObjectExport {
169    /// Location of the Outer of this object. (C++ name: `OuterIndex`)
170    outer_index: i32,
171    /// The name of the object we are exporting. (C++ name: `ObjectName`)
172    pub object_name: NameReference,
173
174    /// If this is not a class, an ObjectReference for the class of this export
175    class_index: i32,
176
177    /// If this is a class or a struct, an ObjectReference for the superclass of this export
178    super_index: i32,
179
180    template_index: i32,
181
182    /// Object flags for this export
183    pub object_flags: u32, // TODO: Use ObjectFlags enum
184
185    /// Number of bytes serialized by this export
186    pub serial_size: i64,
187
188    /// Offset of the start of the bytes for this export
189    pub serial_offset: i64,
190
191    /// Relative to serial_offset, beginning of this export's tagged property serialization data
192    pub script_serialization_start_offset: i64,
193
194    /// Relative to serial_offset, end of the tagged property serialization data
195    pub script_serialization_end_offset: i64,
196
197    pub forced_export: bool,
198    pub not_for_client: bool,
199    pub not_for_server: bool,
200
201    /// False if the object is *always* loaded in the editor game (true means maybe)
202    pub not_always_loaded_for_editor_game: bool,
203
204    pub is_asset: bool,
205    pub is_inherited_instance: bool,
206    pub generate_public_hash: bool,
207
208    /// If this is a top level package, the original package file flags
209    pub package_flags: u32, // TODO: Use PackageFlags enum
210
211    // Contiguous blocks with offsets relative to each other, -1 means "not present"
212    pub first_export_dependency: i32,
213    pub serialization_before_serialization_dependencies: i32,
214    pub create_before_serialization_dependencies: i32,
215    pub serialization_before_create_dependencies: i32,
216    pub create_before_create_dependencies: i32,
217}
218
219impl ObjectExport {
220    /// Determine where the Outer for this export lives
221    pub fn outer(&self) -> ObjectReference {
222        ObjectReference::from(self.outer_index)
223    }
224
225    /// Determine where the Class for this export lives, if it's not a class export
226    pub fn class(&self) -> ObjectReference {
227        ObjectReference::from(self.class_index)
228    }
229
230    /// Determine where the Super for this export lives, if it's a class export
231    pub fn superclass(&self) -> ObjectReference {
232        ObjectReference::from(self.super_index)
233    }
234
235    pub fn template(&self) -> ObjectReference {
236        ObjectReference::from(self.template_index)
237    }
238}
239
240/// A reference to an object in another package. Typically accessed through [`AssetHeader::package_import_iter`], but you can also
241/// manually resolve the [`NameReference`]s. (C++ name: `FObjectImport`)
242#[derive(Debug)]
243pub struct ObjectImport {
244    /// Location of the Outer of this object. (C++ name: `OuterIndex`)
245    outer_index: i32,
246    /// The name of the object we are importing. (C++ name: `ObjectName`)
247    pub object_name: NameReference,
248    /// The name of the package that contains the class of the object we're importing. (C++ name: `ClassPackage`)
249    pub class_package: NameReference,
250    /// The name of the class of the object we're importing. (C++ name: `ClassName`)
251    pub class_name: NameReference,
252    /// Package name this import belongs to (C++ name: `PackageName`)
253    pub package_name: Option<NameReference>,
254    /// Does this import come from an optional package (C++ name: `bImportOptional`)
255    pub import_optional: bool,
256}
257
258impl ObjectImport {
259    /// Determine where the Outer for this import lives
260    pub fn outer(&self) -> ObjectReference {
261        ObjectReference::from(self.outer_index)
262    }
263}
264
265/// Iterator over the imported packages in a given [`AssetHeader`]
266pub struct ImportIterator<'a, R> {
267    package: &'a AssetHeader<R>,
268    next_index: usize,
269    package_name_reference: NameReference,
270    core_uobject_package_name_reference: Option<NameReference>,
271}
272
273impl<'a, R> ImportIterator<'a, R> {
274    pub fn new(package: &'a AssetHeader<R>) -> Self {
275        let package_name_reference = package.find_name("Package");
276        let core_uobject_package_name_reference = package.find_name("/Script/CoreUObject");
277
278        // If we can't find the "Package" name in the package, then there can't be any imports referencing it, so return an iterator
279        // that'll return zero elements.
280        match package_name_reference {
281            Some(package_name_reference) => Self {
282                package,
283                next_index: 0,
284                package_name_reference,
285                core_uobject_package_name_reference,
286            },
287            None => Self {
288                package,
289                next_index: package.imports.len(),
290                package_name_reference: NameReference {
291                    index: 0,
292                    number: None,
293                },
294                core_uobject_package_name_reference,
295            },
296        }
297    }
298}
299
300impl<'a, R> Iterator for ImportIterator<'a, R> {
301    type Item = String;
302
303    fn next(&mut self) -> Option<Self::Item> {
304        while self.next_index < self.package.imports.len() {
305            let import = &self.package.imports[self.next_index];
306            self.next_index += 1;
307            if import.class_name == self.package_name_reference
308                && self
309                    .core_uobject_package_name_reference
310                    .map_or(false, |n| import.object_name != n)
311            {
312                return Some(
313                    self.package
314                        .resolve_name(&import.object_name)
315                        .unwrap()
316                        .to_string(),
317                );
318            }
319        }
320
321        None
322    }
323}
324
325/// Represents the metadata about a thumbnail for an asset, stored behind [`AssetHeader::thumbnail_table_offsets`] (see `ThumbnailTools::LoadThumbnailsFromPackageInternal`)
326#[derive(Debug)]
327pub struct ThumbnailInfo {
328    pub object_class_name: String,
329    pub object_path_without_package_name: String,
330    pub file_offset: i32,
331}
332
333/// A table of contents for a uasset loaded from disk, containing all the shared package summary information.
334/// This roughly maps to `FPackageFileSummary` in Engine/Source/Runtime/CoreUObject/Public/UObject/PackageFileSummary.h, except we
335/// load some of the indirectly referenced data (i.e. names, imports, exports).
336#[derive(Debug)]
337pub struct AssetHeader<R> {
338    pub archive: Archive<R>,
339    /// Full size of the asset header (C++ name: `TotalHeaderSize`)
340    pub total_header_size: i32,
341    /// The last name this package was saved with (C++ name: `PackageName`)
342    pub package_name: String,
343    /// Package flags like whether this was serialized for the editor (C++ name: `PackagesFlags`)
344    pub package_flags: u32, // TODO: Use PackageFlags enum
345    /// Table of names used by this asset (C++ name: `NameCount` and `NameOffset`)
346    pub names: Vec<String>,
347    /// Number of soft object paths references contained in this package (C++ name: `SoftObjectPathsCount`)
348    pub soft_object_paths_count: i32,
349    /// Location into the file on disk for the soft object paths reference list (C++ name: `SoftObjectPathsOffset`)
350    pub soft_object_paths_offset: i32,
351    /// Localization ID for this package (C++ name: `LocalizationId`)
352    pub localization_id: Option<String>,
353    /// Number of gatherable text data entries (C++ name: `GatherableTextDataCount`)
354    pub gatherable_text_data_count: i32,
355    /// Location on disk of gatherable text data entries (C++ name: `GatherableTextDataOffset`)
356    pub gatherable_text_data_offset: i32,
357    /// Exports (objects) listed by this asset (C++ name: `ExportCount` and `ExportOffset`)
358    pub exports: Vec<ObjectExport>,
359    /// Imports (dependencies) listed by this asset (C++ name: `ImportCount` and `ImportOffset`)
360    pub imports: Vec<ObjectImport>,
361    /// Location of DependsMap data (C++ name: `DependsOffset`)
362    pub depends_offset: i32,
363    /// Number of soft package references that are listed (C++ name: `SoftPackageReferencesCount`)
364    pub soft_package_references_count: i32,
365    /// Location on disk of the soft package references (C++ name: `SoftPackageReferencesOffset`)
366    pub soft_package_references_offset: i32,
367    /// Location of SearchableNamesMap data (C++ name: `SearchableNamesOffset`)
368    pub searchable_names_offset: Option<i32>,
369    /// Offset of the thumbnail table (C++ name: `ThumbnailTableOffset`)
370    pub thumbnail_table_offset: i32,
371    /// Information about the engine version the asset was saved with (C++ name: `SavedByEngineVersion`)
372    pub engine_version: UnrealEngineVersion,
373    /// Information about the engine version the asset is compatible with (for hotfix support) (C++ name: `CompatibleWithEngineVersion`)
374    pub compatible_with_engine_version: UnrealEngineVersion,
375    /// Flags dictating compression settings for this asset (C++ name: `CompressionFlags`)
376    pub compression_flags: u32,
377    /// This is a random number in assets created by the shipping build of the editor, and a crc32 of the uppercased filename
378    /// otherwise. Weird. Used to determine if an asset was made "by a modder or by Epic (or licensee)". (C++ name: `PackageSource`)
379    pub package_source: u32,
380    /// No longer used
381    pub additional_packages_to_cook: Vec<String>,
382    /// No longer used
383    pub texture_allocations: Option<i32>,
384    /// Location on disk of the asset registry tag data (C++ name: `AssetRegistryDataOffset`)
385    pub asset_registry_data_offset: i32,
386    /// Offset to the location in the file where the bulkdata starts  (C++ name: `BulkDataStartOffset`)
387    pub bulk_data_start_offset: i64,
388    /// Offset to the location in the file where the FWorldTileInfo data starts (C++ name: `WorldTileInfoDataOffset`)
389    pub world_tile_info_data_offset: Option<i32>,
390    /// Streaming install ChunkIDs (C++ name: `ChunkIDs`)
391    pub chunk_ids: Vec<i32>,
392    /// Number of preload dependency data entries (C++ name: `PreloadDependencyCount`)
393    pub preload_dependency_count: i32,
394    /// Location into the file on disk for the preload dependency data (C++ name: `PreloadDependencyOffset`)
395    pub preload_dependency_offset: i32,
396    /// Number of names that are referenced from serialized export data (sorted first in the name map) (C++ name: NamesReferencedFromExportDataCount`)
397    pub names_referenced_from_export_data_count: i32,
398    /// Location into the file on disk for the payload table of contents data (C++ name: `PayloadTocOffset`)
399    pub payload_toc_offset: i64,
400    /// Location into the file of the data resource(s) (C++ name: `DataResourceOffset `)
401    pub data_resource_offset: Option<i32>,
402}
403
404impl<R> AssetHeader<R>
405where
406    R: Seek + Read,
407{
408    /// Parse an [`AssetHeader`] from the given reader, assuming a little endian uasset
409    pub fn new(reader: R) -> Result<Self> {
410        let mut archive = Archive::new(reader)?;
411
412        // Parse and seek past `CustomVersionContainer`
413        let custom_versions_stream_info = ArrayStreamInfo::from_current_position(&mut archive)?;
414        match archive.custom_version_serialization_format() {
415            CustomVersionSerializationFormat::Guids => {
416                let _custom_versions = UnrealArray::<UnrealGuidCustomVersion>::seek_past_with_info(
417                    &mut archive,
418                    &custom_versions_stream_info,
419                )?;
420            }
421            CustomVersionSerializationFormat::Optimized => {
422                let _custom_versions = UnrealArray::<UnrealCustomVersion>::seek_past_with_info(
423                    &mut archive,
424                    &custom_versions_stream_info,
425                )?;
426            }
427        }
428
429        let total_header_size = archive.read_le()?;
430
431        let package_name = UnrealString::parse_inline(&mut archive)?;
432
433        let package_flags = archive.read_le()?;
434        let has_editor_only_data = (package_flags & PackageFlags::FilterEditorOnly as u32) == 0;
435        archive.with_editoronly_data = has_editor_only_data;
436
437        let names = if archive.serialized_with(ObjectVersion::VER_UE4_NAME_HASHES_SERIALIZED) {
438            UnrealArray::<UnrealNameEntryWithHash>::parse_indirect(&mut archive)?
439        } else {
440            UnrealArray::<UnrealString>::parse_indirect(&mut archive)?
441        };
442
443        // This is an indirect array of `FSoftObjectPath` entries, which we could parse.
444        let has_soft_object_paths =
445            archive.serialized_with(ObjectVersionUE5::ADD_SOFTOBJECTPATH_LIST);
446        let (soft_object_paths_count, soft_object_paths_offset) = if has_soft_object_paths {
447            (archive.read_le()?, archive.read_le()?)
448        } else {
449            (0, 0)
450        };
451
452        let supports_localization_id =
453            archive.serialized_with(ObjectVersion::VER_UE4_ADDED_PACKAGE_SUMMARY_LOCALIZATION_ID);
454        let localization_id = if supports_localization_id && has_editor_only_data {
455            Some(UnrealString::parse_inline(&mut archive)?)
456        } else {
457            None
458        };
459
460        let has_gatherable_text_data =
461            archive.serialized_with(ObjectVersion::VER_UE4_SERIALIZE_TEXT_IN_PACKAGES);
462        let (gatherable_text_data_count, gatherable_text_data_offset) = if has_gatherable_text_data
463        {
464            (archive.read_le()?, archive.read_le()?)
465        } else {
466            (0, 0)
467        };
468
469        let exports = UnrealArray::<UnrealObjectExport>::parse_indirect(&mut archive)?;
470
471        let imports = UnrealArray::<UnrealClassImport>::parse_indirect(&mut archive)?;
472
473        let depends_offset = archive.read_le()?;
474
475        let has_string_asset_references_map =
476            archive.serialized_with(ObjectVersion::VER_UE4_ADD_STRING_ASSET_REFERENCES_MAP);
477        let (soft_package_references_count, soft_package_references_offset) =
478            if has_string_asset_references_map {
479                (archive.read_le()?, archive.read_le()?)
480            } else {
481                (0, 0)
482            };
483
484        let has_searchable_names =
485            archive.serialized_with(ObjectVersion::VER_UE4_ADDED_SEARCHABLE_NAMES);
486        let searchable_names_offset = if has_searchable_names {
487            Some(archive.read_le()?)
488        } else {
489            None
490        };
491
492        let thumbnail_table_offset = archive.read_le()?;
493
494        let _guid = UnrealGuid::seek_past(&mut archive)?;
495        let supports_package_owner =
496            archive.serialized_with(ObjectVersion::VER_UE4_ADDED_PACKAGE_OWNER);
497        if supports_package_owner && has_editor_only_data {
498            let _persistent_guid = UnrealGuid::seek_past(&mut archive)?;
499            let before_non_outer_package_import =
500                archive.serialized_without(ObjectVersion::VER_UE4_NON_OUTER_PACKAGE_IMPORT);
501            if before_non_outer_package_import {
502                let _owner_persistent_guid = UnrealGuid::seek_past(&mut archive)?;
503            }
504        }
505
506        let num_generations: i32 = archive.read_le()?;
507        let generations_stream_info = ArrayStreamInfo {
508            offset: archive.stream_position()?,
509            count: num_generations as u64,
510        };
511        let _generations = UnrealArray::<UnrealGenerationInfo>::seek_past_with_info(
512            &mut archive,
513            &generations_stream_info,
514        )?;
515
516        let has_engine_version_object =
517            archive.serialized_with(ObjectVersion::VER_UE4_ENGINE_VERSION_OBJECT);
518        let engine_version = if has_engine_version_object {
519            UnrealEngineVersion::parse_inline(&mut archive)?
520        } else {
521            let engine_changelist: u32 = archive.read_le()?;
522            // 4.26 converts this using FEngineVersion::Set(4, 0, 0, EngineChangelist, TEXT(""));
523            UnrealEngineVersion::from_changelist(engine_changelist)
524        };
525
526        let has_compatible_with_engine_version = archive
527            .serialized_with(ObjectVersion::VER_UE4_PACKAGE_SUMMARY_HAS_COMPATIBLE_ENGINE_VERSION);
528        let compatible_with_engine_version = if has_compatible_with_engine_version {
529            UnrealEngineVersion::parse_inline(&mut archive)?
530            // TODO: Fixup `FixCorruptEngineVersion` for VER_UE4_CORRECT_LICENSEE_FLAG ("The move of EpicInternal.txt in CL 12740027 broke checks for non-licensee builds in UGS.")
531        } else {
532            // 4.27 just copies the engine version here
533            engine_version.clone()
534        };
535
536        let compression_flags = archive.read_le()?;
537
538        // The engine will refuse to load any package with compressed chunks, but it doesn't hurt for us to just skip past them.
539        let num_compressed_chunks: i32 = archive.read_le()?;
540        let compressed_chunk_stream_info = ArrayStreamInfo {
541            offset: archive.stream_position()?,
542            count: num_compressed_chunks as u64,
543        };
544        let _compressed_chunks = UnrealArray::<UnrealCompressedChunk>::seek_past_with_info(
545            &mut archive,
546            &compressed_chunk_stream_info,
547        )?;
548
549        let package_source = archive.read_le()?;
550
551        let additional_packages_to_cook = UnrealArray::<UnrealString>::parse_inline(&mut archive)?;
552
553        let texture_allocations = if archive.legacy_version > -7 {
554            Some(archive.read_le()?)
555        } else {
556            None
557        };
558
559        let asset_registry_data_offset = archive.read_le()?;
560        let bulk_data_start_offset = archive.read_le()?;
561
562        let has_world_tile_info_data =
563            archive.serialized_with(ObjectVersion::VER_UE4_WORLD_LEVEL_INFO);
564        let world_tile_info_data_offset = if has_world_tile_info_data {
565            let offset = archive.read_le()?;
566            if offset > 0 {
567                Some(offset)
568            } else {
569                None
570            }
571        } else {
572            None
573        };
574
575        let has_chunkid =
576            archive.serialized_with(ObjectVersion::VER_UE4_ADDED_CHUNKID_TO_ASSETDATA_AND_UPACKAGE);
577        let has_chunkid_array = has_chunkid
578            && archive
579                .serialized_with(ObjectVersion::VER_UE4_CHANGED_CHUNKID_TO_BE_AN_ARRAY_OF_CHUNKIDS);
580
581        let chunk_ids = if has_chunkid_array {
582            UnrealArray::<i32>::parse_inline(&mut archive)?
583        } else if has_chunkid {
584            let chunk_id = archive.read_le()?;
585            if chunk_id >= 0 {
586                vec![chunk_id]
587            } else {
588                vec![]
589            }
590        } else {
591            vec![]
592        };
593
594        let has_preload_dependencies =
595            archive.serialized_with(ObjectVersion::VER_UE4_PRELOAD_DEPENDENCIES_IN_COOKED_EXPORTS);
596        let (preload_dependency_count, preload_dependency_offset) = if has_preload_dependencies {
597            (archive.read_le()?, archive.read_le()?)
598        } else {
599            (-1, 0)
600        };
601
602        let has_names_referenced_from_export_data =
603            archive.serialized_with(ObjectVersionUE5::NAMES_REFERENCED_FROM_EXPORT_DATA);
604        let names_referenced_from_export_data_count = if has_names_referenced_from_export_data {
605            archive.read_le()?
606        } else {
607            names.len() as i32
608        };
609
610        let has_payload_toc = archive.serialized_with(ObjectVersionUE5::PAYLOAD_TOC);
611        let payload_toc_offset = if has_payload_toc {
612            archive.read_le()?
613        } else {
614            -1
615        };
616
617        let has_data_resource_offset = archive.serialized_with(ObjectVersionUE5::DATA_RESOURCES);
618        let data_resource_offset = if has_data_resource_offset {
619            let offset = archive.read_le()?;
620            if offset > 0 {
621                Some(offset)
622            } else {
623                None
624            }
625        } else {
626            None
627        };
628
629        Ok(Self {
630            archive,
631            total_header_size,
632            package_name,
633            package_flags,
634            names,
635            soft_object_paths_count,
636            soft_object_paths_offset,
637            localization_id,
638            gatherable_text_data_count,
639            gatherable_text_data_offset,
640            exports,
641            imports,
642            depends_offset,
643            soft_package_references_count,
644            soft_package_references_offset,
645            searchable_names_offset,
646            thumbnail_table_offset,
647            engine_version,
648            compatible_with_engine_version,
649            compression_flags,
650            package_source,
651            additional_packages_to_cook,
652            texture_allocations,
653            asset_registry_data_offset,
654            bulk_data_start_offset,
655            world_tile_info_data_offset,
656            chunk_ids,
657            preload_dependency_count,
658            preload_dependency_offset,
659            names_referenced_from_export_data_count,
660            payload_toc_offset,
661            data_resource_offset,
662        })
663    }
664}
665
666impl<R> AssetHeader<R> {
667    /// Attempt to look up `find_name` in the name table serialized in [`AssetHeader::names`], will return None
668    /// if the name does not exist. Names are case insensitive.
669    pub fn find_name(&self, find_name: &str) -> Option<NameReference> {
670        // TODO: Handle `_N` suffixes -> number: Some?
671        let find_name_lower = find_name.to_lowercase();
672        for (index, name) in self.names.iter().enumerate() {
673            if find_name == name || find_name_lower == name.to_lowercase() {
674                return Some(NameReference {
675                    index: index as u32,
676                    number: None,
677                });
678            }
679        }
680
681        None
682    }
683
684    /// Look up the string representation for a given [`NameReference`].
685    pub fn resolve_name<'a>(
686        &'a self,
687        name_reference: &NameReference,
688    ) -> std::result::Result<Cow<'a, str>, InvalidNameIndexError> {
689        let index = name_reference.index as usize;
690        if self.names.len() > index {
691            let mut name = Cow::from(&self.names[index]);
692            if let Some(number) = name_reference.number {
693                name.to_mut().push_str(&format!("_{}", number.get() - 1));
694            }
695            Ok(name)
696        } else {
697            Err(InvalidNameIndexError(name_reference.index))
698        }
699    }
700
701    /// Create an iterator over the names of just the packages imported by this asset (i.e. its dependencies).
702    pub fn package_import_iter(&self) -> ImportIterator<'_, R> {
703        ImportIterator::new(self)
704    }
705}
706
707impl<R> AssetHeader<R>
708where
709    R: Seek + Read,
710{
711    /// Create an iterator over the names of just the packages imported by this asset (i.e. its dependencies).
712    pub fn thumbnail_iter(&mut self) -> Result<UnrealArrayIterator<'_, UnrealThumbnailInfo, R>> {
713        self.archive
714            .seek(SeekFrom::Start(self.thumbnail_table_offset as u64))?;
715
716        let stream_info = ArrayStreamInfo::from_current_position(&mut self.archive)?;
717        UnrealArrayIterator::new(self, stream_info)
718    }
719}