1#![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#![allow(
104 clippy::let_unit_value, )]
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#[derive(Clone, Copy, Debug, PartialEq)]
135pub struct NameReference {
136 pub index: u32,
138 pub number: Option<NonZeroU32>,
141}
142
143#[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#[derive(Debug)]
168pub struct ObjectExport {
169 outer_index: i32,
171 pub object_name: NameReference,
173
174 class_index: i32,
176
177 super_index: i32,
179
180 template_index: i32,
181
182 pub object_flags: u32, pub serial_size: i64,
187
188 pub serial_offset: i64,
190
191 pub script_serialization_start_offset: i64,
193
194 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 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 pub package_flags: u32, 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 pub fn outer(&self) -> ObjectReference {
222 ObjectReference::from(self.outer_index)
223 }
224
225 pub fn class(&self) -> ObjectReference {
227 ObjectReference::from(self.class_index)
228 }
229
230 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#[derive(Debug)]
243pub struct ObjectImport {
244 outer_index: i32,
246 pub object_name: NameReference,
248 pub class_package: NameReference,
250 pub class_name: NameReference,
252 pub package_name: Option<NameReference>,
254 pub import_optional: bool,
256}
257
258impl ObjectImport {
259 pub fn outer(&self) -> ObjectReference {
261 ObjectReference::from(self.outer_index)
262 }
263}
264
265pub 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 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#[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#[derive(Debug)]
337pub struct AssetHeader<R> {
338 pub archive: Archive<R>,
339 pub total_header_size: i32,
341 pub package_name: String,
343 pub package_flags: u32, pub names: Vec<String>,
347 pub soft_object_paths_count: i32,
349 pub soft_object_paths_offset: i32,
351 pub localization_id: Option<String>,
353 pub gatherable_text_data_count: i32,
355 pub gatherable_text_data_offset: i32,
357 pub exports: Vec<ObjectExport>,
359 pub imports: Vec<ObjectImport>,
361 pub depends_offset: i32,
363 pub soft_package_references_count: i32,
365 pub soft_package_references_offset: i32,
367 pub searchable_names_offset: Option<i32>,
369 pub thumbnail_table_offset: i32,
371 pub engine_version: UnrealEngineVersion,
373 pub compatible_with_engine_version: UnrealEngineVersion,
375 pub compression_flags: u32,
377 pub package_source: u32,
380 pub additional_packages_to_cook: Vec<String>,
382 pub texture_allocations: Option<i32>,
384 pub asset_registry_data_offset: i32,
386 pub bulk_data_start_offset: i64,
388 pub world_tile_info_data_offset: Option<i32>,
390 pub chunk_ids: Vec<i32>,
392 pub preload_dependency_count: i32,
394 pub preload_dependency_offset: i32,
396 pub names_referenced_from_export_data_count: i32,
398 pub payload_toc_offset: i64,
400 pub data_resource_offset: Option<i32>,
402}
403
404impl<R> AssetHeader<R>
405where
406 R: Seek + Read,
407{
408 pub fn new(reader: R) -> Result<Self> {
410 let mut archive = Archive::new(reader)?;
411
412 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 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 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 } else {
532 engine_version.clone()
534 };
535
536 let compression_flags = archive.read_le()?;
537
538 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 pub fn find_name(&self, find_name: &str) -> Option<NameReference> {
670 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 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 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 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}