trumank / uesave-rs

Rust library to read and write Unreal Engine save files
MIT License
269 stars 53 forks source link

Cannot parse NARUTO TO BORUTO: SHINOBI STRIKER ps4 savedata #38

Closed Zhaxxy closed 3 weeks ago

Zhaxxy commented 5 months ago

Sorry if im not being very helpful here as im not familar with the game nor unreal saves or rust but i tried to do to-json with the ue4savegame.ps4.sav found in the decrypted save file in the zip attached but i get an error

./uesave to-json -i ue4savegame.ps4.sav Error: at offset 569823: io error: failed to fill whole buffer

could you please look at the save and see if you can add support for it? or perhaps i need to use the -t option but i really dont know, i know the save has the GSAV header though

thank you soo much for this tool

the save file is in this zip, its the ue4savegame.ps4.sav, the other stuff is just ps4 savedata metadata thats in all ps4 saves 15_04_2024__01_32_16.zip

trumank commented 5 months ago

Some structs have custom serialization which adds extra data before or after the properties. This patch seems to allow this particular save to parse but probably is not general. The serialized struct sizes in in the save seem to be different after round trip so the object nesting might still be wrong, but would probably have to decompile the game's executable to dig further.

diff --git a/src/lib.rs b/src/lib.rs
index c89ac32..3c9245f 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1536,7 +1536,13 @@ pub enum StructValue {
     SoftObjectPath(String, String),
     GameplayTagContainer(GameplayTagContainer),
     /// User defined struct which is simply a list of properties
-    Struct(Properties),
+    Struct {
+        properties: Properties,
+        #[serde(default, skip_serializing_if = "Vec::is_empty")]
+        pre: Vec<u8>,
+        #[serde(default, skip_serializing_if = "Vec::is_empty")]
+        post: Vec<u8>,
+    },
 }

 /// Vectorized properties to avoid storing the variant with each value
@@ -1668,7 +1674,28 @@ impl StructValue {
                 StructValue::GameplayTagContainer(GameplayTagContainer::read(reader)?)
             }

-            StructType::Struct(_) => StructValue::Struct(read_properties_until_none(reader)?),
+            StructType::Struct(name) => {
+                let pre = if name.as_deref() == Some("NN_ServerCharacterID") {
+                    let mut buf = vec![0; 8];
+                    reader.read_exact(&mut buf)?;
+                    buf
+                } else {
+                    vec![]
+                };
+                let properties = read_properties_until_none(reader)?;
+                let post = if name.as_deref() == Some("NN_GameData_ForSave") {
+                    let mut buf = vec![0; 278];
+                    reader.read_exact(&mut buf)?;
+                    buf
+                } else {
+                    vec![]
+                };
+                StructValue::Struct {
+                    properties,
+                    pre,
+                    post,
+                }
+            }
         })
     }
     fn write<W: Write>(&self, writer: &mut Context<W>) -> TResult<()> {
@@ -1690,7 +1717,15 @@ impl StructValue {
                 write_string(writer, b)?;
             }
             StructValue::GameplayTagContainer(v) => v.write(writer)?,
-            StructValue::Struct(v) => write_properties_none_terminated(writer, v)?,
+            StructValue::Struct {
+                properties,
+                pre,
+                post,
+            } => {
+                writer.write_all(pre)?;
+                write_properties_none_terminated(writer, properties)?;
+                writer.write_all(post)?;
+            }
         }
         Ok(())
     }
Zhaxxy commented 3 months ago

Some structs have custom serialization which adds extra data before or after the properties. This patch seems to allow this particular save to parse but probably is not general. The serialized struct sizes in in the save seem to be different after round trip so the object nesting might still be wrong, but would probably have to decompile the game's executable to dig further.

diff --git a/src/lib.rs b/src/lib.rs
index c89ac32..3c9245f 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1536,7 +1536,13 @@ pub enum StructValue {
     SoftObjectPath(String, String),
     GameplayTagContainer(GameplayTagContainer),
     /// User defined struct which is simply a list of properties
-    Struct(Properties),
+    Struct {
+        properties: Properties,
+        #[serde(default, skip_serializing_if = "Vec::is_empty")]
+        pre: Vec<u8>,
+        #[serde(default, skip_serializing_if = "Vec::is_empty")]
+        post: Vec<u8>,
+    },
 }

 /// Vectorized properties to avoid storing the variant with each value
@@ -1668,7 +1674,28 @@ impl StructValue {
                 StructValue::GameplayTagContainer(GameplayTagContainer::read(reader)?)
             }

-            StructType::Struct(_) => StructValue::Struct(read_properties_until_none(reader)?),
+            StructType::Struct(name) => {
+                let pre = if name.as_deref() == Some("NN_ServerCharacterID") {
+                    let mut buf = vec![0; 8];
+                    reader.read_exact(&mut buf)?;
+                    buf
+                } else {
+                    vec![]
+                };
+                let properties = read_properties_until_none(reader)?;
+                let post = if name.as_deref() == Some("NN_GameData_ForSave") {
+                    let mut buf = vec![0; 278];
+                    reader.read_exact(&mut buf)?;
+                    buf
+                } else {
+                    vec![]
+                };
+                StructValue::Struct {
+                    properties,
+                    pre,
+                    post,
+                }
+            }
         })
     }
     fn write<W: Write>(&self, writer: &mut Context<W>) -> TResult<()> {
@@ -1690,7 +1717,15 @@ impl StructValue {
                 write_string(writer, b)?;
             }
             StructValue::GameplayTagContainer(v) => v.write(writer)?,
-            StructValue::Struct(v) => write_properties_none_terminated(writer, v)?,
+            StructValue::Struct {
+                properties,
+                pre,
+                post,
+            } => {
+                writer.write_all(pre)?;
+                write_properties_none_terminated(writer, properties)?;
+                writer.write_all(post)?;
+            }
         }
         Ok(())
     }

sorry for taking long to reply i forgot about this, but yes youre right, it does work with that save i sent with the changes to the lib.rs folder, but this save, i get similar error even with the new changes

uesave.exe to-json -i ue4savegame.ps4.sav -o ue4savegame.ps4.sav.json Error: at offset 557939: io error: failed to fill whole buffer

19_05_2024__23_12_56.zip (theres 2 saves here but i only tried it with the Customizev003 one (PS4_FOLDER_IN_ME_PS4_SAVEDATA_1924e1ae0638ed9b_CUSA08789Customizev003)