Find the magical missing bytes at the end of Persistent.sav

Turns out there can be multiple compressed blocks in a single .sav file,
so now we read all of them.
This commit is contained in:
Joshua Goins 2025-02-24 18:15:54 -05:00
parent 153c8610c9
commit be82857aba
2 changed files with 52 additions and 23 deletions

View file

@ -11,22 +11,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut data = Cursor::new(std::fs::read(&args[1])?);
let compressed = CompressedSaveFile::read_le(&mut data)?;
let mut output = vec![];
let mut uncompressed = vec![0; compressed.a_uncompresed_size as usize];
let decompress = Decompress::new(true);
let mut d = ZlibDecoder::new_with_decompress(&*compressed.compressed_data, decompress);
let size = d.read(&mut uncompressed)?;
output.extend_from_slice(&uncompressed[..size]);
std::fs::write("output.bin", &output)?;
let mut cursor = Cursor::new(&uncompressed);
let save_data = TaggedSerialization::read_le(&mut cursor);
println!("{:#?}", save_data);
println!("{:#?}", compressed);
Ok(())
}

View file

@ -15,18 +15,22 @@ pub mod str_property;
pub mod struct_property;
mod structs;
use std::fs::write;
use std::io::{Cursor, Read};
use binrw::helpers::until_eof;
use crate::array_property::ArrayProperty;
use crate::array_property::{ArrayEntry, ArrayKeyData, ArrayProperty};
use crate::bool_property::BoolProperty;
use crate::common::{read_string_with_length, write_string_with_length};
use crate::float_property::FloatProperty;
use crate::int_property::IntProperty;
use crate::map_property::MapProperty;
use crate::map_property::{KeyType, MapProperty};
use crate::set_property::SetProperty;
use crate::str_property::StrProperty;
use crate::struct_property::StructProperty;
use binrw::{BinRead, BinResult, binrw};
use flate2::bufread::ZlibDecoder;
use flate2::Decompress;
// Used in ArrayProperty exclusively, but could be used instead of magic above
#[binrw]
@ -113,16 +117,56 @@ pub struct TaggedSerialization {
pub objs: Vec<TaggedObject>,
}
#[binrw::parser(reader, endian)]
fn read_compressed_data(
compressed_size: u64,
uncompressed_size: u64,
) -> BinResult<Vec<u8>> {
let mut compressed_data = vec![0; compressed_size as usize];
reader.read_exact(&mut compressed_data)?;
let mut uncompressed = vec![0; uncompressed_size as usize];
let mut d = ZlibDecoder::new(&*compressed_data);
let size = d.read(&mut uncompressed)?;
Ok(uncompressed[..size].to_vec())
}
// TODO: there's no point in using a parser, we should just use map()
#[binrw::parser(reader, endian)]
fn read_tagged_data(
data_blocks: &Vec<CompressedBlock>
) -> BinResult<TaggedSerialization> {
let data_vecs: Vec<Vec<u8>> = data_blocks.iter().map(|x| x.data.clone()).collect();
let combined_data = data_vecs.concat();
write("output.bin", &combined_data);
let mut cursor = Cursor::new(&combined_data);
TaggedSerialization::read_le(&mut cursor)
}
#[binrw]
#[brw(magic = 0x9e2a83c1u32)]
#[derive(Debug)]
pub struct CompressedSaveFile {
#[br(pad_before = 4)]
pub compressed_size: u64,
pub struct CompressedBlock {
#[br(pad_before = 6)]
#[br(pad_after = 5)]
pub unk: u8, // always 2
pub a_compresed_size: u64,
pub a_uncompresed_size: u64,
pub b_compresed_size: u64,
pub b_uncompresed_size: u64,
#[br(count = a_compresed_size)]
pub compressed_data: Vec<u8>,
#[br(parse_with = read_compressed_data, args(a_compresed_size, a_uncompresed_size))]
pub data: Vec<u8>,
}
#[binrw]
#[derive(Debug)]
pub struct CompressedSaveFile {
#[br(parse_with = until_eof)]
pub data: Vec<CompressedBlock>,
#[br(parse_with = read_tagged_data, args(&data))]
pub value: TaggedSerialization,
}