From d577451bc1da417ae3f154f0482a5564301ea363 Mon Sep 17 00:00:00 2001 From: Scott Anderson <662325+scottanderson@users.noreply.github.com> Date: Wed, 11 Sep 2024 18:34:01 -0400 Subject: [PATCH] Create enums for SaveGameVersion and UE5Version --- src/lib.rs | 62 +++++++++++++++++++++++----------------- src/object_version.rs | 47 ++++++++++++++++++++++++++++++ src/savegame_version.rs | 13 +++++++++ tests/common/vector2d.rs | 4 +-- 4 files changed, 97 insertions(+), 29 deletions(-) create mode 100644 src/object_version.rs create mode 100644 src/savegame_version.rs diff --git a/src/lib.rs b/src/lib.rs index 2bd2876..a87f322 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -70,10 +70,14 @@ pub mod engine_version; pub mod error; /// Game version enumeration. pub mod game_version; +/// Object version information. +pub mod object_version; /// Extensions for `Ord`. mod ord_ext; /// Property types. pub mod properties; +/// Savegame version information. +pub mod savegame_version; pub(crate) mod scoped_stack_entry; /// Various types. pub mod types; @@ -98,8 +102,10 @@ use flate2::read::ZlibDecoder; use flate2::write::ZlibEncoder; use flate2::Compression; use indexmap::IndexMap; +use object_version::EUnrealEngineObjectUE5Version; use ord_ext::OrdExt; use properties::{Property, PropertyOptions, PropertyTrait}; +use savegame_version::SaveGameVersion; use types::Guid; /// The four bytes 'GVAS' appear at the beginning of every GVAS file. @@ -125,10 +131,10 @@ pub enum GvasHeader { }, /// Version 3 Version3 { - /// File format version. + /// File format version (UE4). package_file_version: u32, - /// Unknown. - unknown: u32, + /// File format version (UE5). + package_file_version_ue5: u32, /// Unreal Engine version. engine_version: FEngineVersion, /// Custom version format. @@ -171,7 +177,10 @@ impl GvasHeader { } let save_game_file_version = cursor.read_u32::()?; - if !save_game_file_version.between(2, 3) { + if !save_game_file_version.between( + SaveGameVersion::AddedCustomVersions as u32, + SaveGameVersion::PackageFileSummaryVersionChange as u32, + ) { Err(DeserializeError::InvalidHeader( format!("GVAS version {save_game_file_version} not supported").into_boxed_str(), ))? @@ -186,9 +195,21 @@ impl GvasHeader { } // This field is only present in the v3 header - let unknown = match save_game_file_version { - 3 => Some(cursor.read_u32::()?), - _ => None, + let package_file_version_ue5 = if save_game_file_version + >= SaveGameVersion::PackageFileSummaryVersionChange as u32 + { + let version = cursor.read_u32::()?; + if !version.between( + EUnrealEngineObjectUE5Version::InitialVersion as u32, + EUnrealEngineObjectUE5Version::DataResources as u32, + ) { + Err(DeserializeError::InvalidHeader( + format!("UE5 Package file version {version} is not supported").into_boxed_str(), + ))? + } + Some(version) + } else { + None }; let engine_version = FEngineVersion::read(cursor)?; @@ -209,7 +230,7 @@ impl GvasHeader { let save_game_class_name = cursor.read_string()?; - Ok(match unknown { + Ok(match package_file_version_ue5 { None => GvasHeader::Version2 { package_file_version, engine_version, @@ -217,9 +238,9 @@ impl GvasHeader { custom_versions, save_game_class_name, }, - Some(unknown) => GvasHeader::Version3 { + Some(package_file_version_ue5) => GvasHeader::Version3 { package_file_version, - unknown, + package_file_version_ue5, engine_version, custom_version_format, custom_versions, @@ -271,7 +292,7 @@ impl GvasHeader { GvasHeader::Version3 { package_file_version, - unknown, + package_file_version_ue5, engine_version, custom_version_format, custom_versions, @@ -280,7 +301,7 @@ impl GvasHeader { let mut len = 24; cursor.write_u32::(3)?; cursor.write_u32::(*package_file_version)?; - cursor.write_u32::(*unknown)?; + cursor.write_u32::(*package_file_version_ue5)?; len += engine_version.write(cursor)?; cursor.write_u32::(*custom_version_format)?; cursor.write_u32::(custom_versions.len() as u32)?; @@ -329,21 +350,8 @@ trait GvasHeaderTrait { impl GvasHeaderTrait for GvasHeader { fn use_large_world_coordinates(&self) -> bool { match self { - GvasHeader::Version2 { - package_file_version: _, - engine_version: _, - custom_version_format: _, - custom_versions: _, - save_game_class_name: _, - } => false, - GvasHeader::Version3 { - package_file_version: _, - unknown: _, - engine_version: _, - custom_version_format: _, - custom_versions: _, - save_game_class_name: _, - } => true, + GvasHeader::Version2 { .. } => false, + GvasHeader::Version3 { .. } => true, } } } diff --git a/src/object_version.rs b/src/object_version.rs new file mode 100644 index 0000000..fd873b3 --- /dev/null +++ b/src/object_version.rs @@ -0,0 +1,47 @@ +use num_enum::IntoPrimitive; + +/// UE5 object versions. +#[derive(IntoPrimitive)] +#[repr(u32)] +pub enum EUnrealEngineObjectUE5Version { + /// The original UE5 version, at the time this was added the UE4 version was 522, so UE5 will start from 1000 to show a clear difference + InitialVersion = 1000, + + /// Support stripping names that are not referenced from export data + NamesReferencedFromExportData, + + /// Added a payload table of contents to the package summary + PayloadToc, + + /// Added data to identify references from and to optional package + OptionalResources, + + /// Large world coordinates converts a number of core types to double components by default. + LargeWorldCoordinates, + + /// Remove package GUID from FObjectExport + RemoveObjectExportPackageGuid, + + /// Add IsInherited to the FObjectExport entry + TrackObjectExportIsInherited, + + /// Replace FName asset path in FSoftObjectPath with (package name, asset name) pair FTopLevelAssetPath + FsoftobjectpathRemoveAssetPathFnames, + + /// Add a soft object path list to the package summary for fast remap + AddSoftobjectpathList, + + /// Added bulk/data resource table + DataResources, + + /// Added script property serialization offset to export table entries for saved, versioned packages + ScriptSerializationOffset, + + /// Adding property tag extension, + /// Support for overridable serialization on UObject, + /// Support for overridable logic in containers + PropertyTagExtensionAndOverridableSerialization, + + /// Added property tag complete type name and serialization type + PropertyTagCompleteTypeName, +} diff --git a/src/savegame_version.rs b/src/savegame_version.rs new file mode 100644 index 0000000..49592b7 --- /dev/null +++ b/src/savegame_version.rs @@ -0,0 +1,13 @@ +use num_enum::IntoPrimitive; + +/// Save Game File Version from FSaveGameFileVersion::Type +#[derive(IntoPrimitive)] +#[repr(u32)] +pub enum SaveGameVersion { + /// Initial version. + InitialVersion = 1, + /// serializing custom versions into the savegame data to handle that type of versioning + AddedCustomVersions = 2, + /// added a new UE5 version number to FPackageFileSummary + PackageFileSummaryVersionChange = 3, +} diff --git a/tests/common/vector2d.rs b/tests/common/vector2d.rs index 5801167..a7a90a1 100644 --- a/tests/common/vector2d.rs +++ b/tests/common/vector2d.rs @@ -23,7 +23,7 @@ pub(crate) fn expected() -> GvasFile { deserialized_game_version: DeserializedGameVersion::Default, header: GvasHeader::Version3 { package_file_version: 522, - unknown: 1009, + package_file_version_ue5: 1009, engine_version: FEngineVersion { major: 5, minor: 3, @@ -809,7 +809,7 @@ pub const VECTOR2D_JSON: &str = r#"{ "header": { "type": "Version3", "package_file_version": 522, - "unknown": 1009, + "package_file_version_ue5": 1009, "engine_version": { "major": 5, "minor": 3,