Various set and map fixes, add structured object support

This commit is contained in:
Joshua Goins 2025-03-02 16:26:38 -05:00
parent 0b461c5b86
commit d622799cfa
8 changed files with 245 additions and 48 deletions

View file

@ -1,5 +1,5 @@
use binrw::BinRead;
use ireko::CompressedSaveFile;
use ireko::{CompressedSaveFile, GenericTaggedObject};
use std::env;
use std::io::Cursor;
@ -8,8 +8,18 @@ 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 compressed = CompressedSaveFile::<GenericTaggedObject>::read_le(&mut data)?;
println!("{:#?}", compressed);
// useful for creating new structed objs
/*for object in &compressed.value.objs {
println!("NEW OBJ");
for entry in &object.entries {
println!("#[paramacro::serialized_field = {:#?}]", entry.name);
println!("unknown: {}", entry.type_name);
println!("");
}
}*/
Ok(())
}

View file

@ -19,13 +19,16 @@ pub mod float_property;
mod guid;
pub mod int_property;
mod linear_color;
mod localprofile;
pub mod map_property;
mod name_property;
mod persistent;
mod primary_asset_id;
mod primary_asset_type;
mod quat;
mod save_slot_info;
pub mod set_property;
mod slot;
pub mod str_property;
pub mod struct_property;
mod structs;
@ -71,10 +74,7 @@ pub enum Property {
#[br(pre_assert("MapProperty" == magic))]
Map(MapProperty),
#[br(pre_assert("SetProperty" == magic))]
Set {
#[br(args(name))]
value: SetProperty,
},
Set(SetProperty),
}
#[binrw]
@ -126,34 +126,24 @@ fn custom_tagged_object_writer(entries: &Vec<Entry>) -> BinResult<()> {
Ok(())
}
#[binrw::parser(reader, endian)]
fn custom_parser(size_in_bytes: u32) -> BinResult<Vec<TaggedObject>> {
let mut result = Vec::<TaggedObject>::new();
let mut current = reader.stream_position()?;
let end = current + size_in_bytes as u64;
while current < end {
let obj = TaggedObject::read_options(reader, endian, ())?;
if obj.size_in_bytes == 0 {
break;
}
result.push(obj);
current = reader.stream_position()?;
}
Ok(result)
}
#[binrw]
#[derive(Debug)]
pub struct TaggedObject {
pub struct GenericTaggedObject {
pub size_in_bytes: u32,
#[br(parse_with = custom_tagged_object_parser, args(size_in_bytes))]
#[bw(write_with = custom_tagged_object_writer)]
pub entries: Vec<Entry>,
}
impl TaggedObject {
#[binrw]
#[derive(Debug)]
pub struct PersistentObject {
pub size_in_bytes: u32,
#[br(pad_after = 4)]
pub profile: persistent::Persistent,
}
impl GenericTaggedObject {
pub fn entry(&self, key: &str) -> Option<&Entry> {
let entries: Vec<&Entry> = self.entries.iter().filter(|e| e.name == key).collect();
entries.first().copied()
@ -162,10 +152,13 @@ impl TaggedObject {
#[binrw]
#[derive(Debug)]
pub struct TaggedSerialization {
pub struct TaggedSerialization<T>
where
for<'a> T: BinRead<Args<'a> = ()> + 'a,
for<'a> T: BinWrite<Args<'a> = ()> + 'a,
{
pub size_in_bytes: u32,
#[br(parse_with = custom_parser, args(size_in_bytes))]
pub objs: Vec<TaggedObject>,
pub objs: T,
}
#[binrw::parser(reader, endian)]
@ -183,14 +176,18 @@ fn read_compressed_data(compressed_size: u64, uncompressed_size: u64) -> BinResu
// 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> {
fn read_tagged_data<T>(data_blocks: &Vec<CompressedBlock>) -> BinResult<TaggedSerialization<T>>
where
for<'a> T: BinRead<Args<'a> = ()> + 'a,
for<'a> T: BinWrite<Args<'a> = ()> + 'a,
{
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)
TaggedSerialization::<T>::read_le(&mut cursor)
}
#[binrw]
@ -210,11 +207,15 @@ pub struct CompressedBlock {
#[binrw]
#[derive(Debug)]
pub struct CompressedSaveFile {
pub struct CompressedSaveFile<T>
where
for<'a> T: BinRead<Args<'a> = ()> + 'a,
for<'a> T: BinWrite<Args<'a> = ()> + 'a,
{
#[br(parse_with = until_eof)]
#[br(temp)]
#[bw(ignore)]
pub data: Vec<CompressedBlock>,
#[br(parse_with = read_tagged_data, args(&data))]
pub value: TaggedSerialization,
pub value: TaggedSerialization<T>,
}

23
src/localprofile.rs Normal file
View file

@ -0,0 +1,23 @@
use crate::{
bool_property::BoolProperty, int_property::IntProperty, map_property::MapProperty,
str_property::StrProperty,
};
#[paramacro::serialized_struct("Transform")]
#[derive(Debug)]
pub struct LocalProfile {
#[paramacro::serialized_field = "SavedDataVersion"]
version: IntProperty,
#[paramacro::serialized_field = "bDemoVersion"]
demo: BoolProperty,
#[paramacro::serialized_field = "RegisteredNameList"]
name_list: MapProperty,
#[paramacro::serialized_field = "SaveGameName"]
name: StrProperty,
#[paramacro::serialized_field = "bUseSaveSlot"]
use_save_slot: BoolProperty,
}

View file

@ -13,6 +13,9 @@ fn cc() -> BinResult<Vec<GenericProperty>> {
loop {
if let Ok(str) = GenericProperty::read_options(reader, endian, ()) {
if str.property_name == "None" {
break;
}
result.push(str);
} else {
break;
@ -47,6 +50,10 @@ pub struct StructMaybeKey {
#[br(args { magic: &struct_name })]
#[brw(pad_before = 17)]
pub r#struct: Struct,
#[br(parse_with = cc)]
#[br(dbg)]
extra_fields: Vec<GenericProperty>,
}
#[binrw]

101
src/persistent.rs Normal file
View file

@ -0,0 +1,101 @@
use crate::{
array_property::ArrayProperty, bool_property::BoolProperty, build_data::DABuildDataStruct,
da_load_option::DALoadOptionStruct, da_tuning_point_data::DATuningPointData,
datetime::DateTimeStruct, float_property::FloatProperty, int_property::IntProperty,
map_property::MapProperty, name_property::NameProperty, save_slot_info::SaveSlotInfoStruct,
set_property::SetProperty, str_property::StrProperty, transform::TransformStruct,
};
#[paramacro::serialized_struct("Transform")]
#[derive(Debug)]
pub struct Persistent {
#[paramacro::serialized_field = "SavedDataVersion"]
version: IntProperty,
#[paramacro::serialized_field = "bDemoVersion"]
demo_version: BoolProperty,
#[paramacro::serialized_field = "Money"]
money: IntProperty,
#[paramacro::serialized_field = "ObtainedItems"]
obtained_items: SetProperty,
#[paramacro::serialized_field = "ItemSlots"]
item_slots: ArrayProperty,
#[paramacro::serialized_field = "CurrentItemSlotNum"]
current_item_slot: IntProperty,
#[paramacro::serialized_field = "NormalItemInventory"]
normal_item_inventory: MapProperty,
#[paramacro::serialized_field = "ModuleInventory"]
module_inventory: MapProperty,
#[paramacro::serialized_field = "PartsInventory"]
parts_inventory: MapProperty,
#[paramacro::serialized_field = "CurrentBuildData"]
current_build_data: DABuildDataStruct,
#[paramacro::serialized_field = "SavedBuildData"]
saved_build_data: ArrayProperty,
#[paramacro::serialized_field = "Palettes"]
palettes: MapProperty,
#[paramacro::serialized_field = "TuningPointData"]
tuning_point_data: DATuningPointData,
#[paramacro::serialized_field = "EventParams"]
event_params: MapProperty,
#[paramacro::serialized_field = "ReachedDistricts"]
reached_districts: SetProperty,
#[paramacro::serialized_field = "ShopBoughtCount"]
shop_bought_count: MapProperty,
#[paramacro::serialized_field = "AcquiredItemBoxIds"]
acquired_item_box_ids: SetProperty,
#[paramacro::serialized_field = "OpenedStrongBoxIds"]
opened_strong_box_ids: SetProperty,
#[paramacro::serialized_field = "RegressionPlayerStartTag"]
regression_player_start_tag: NameProperty,
#[paramacro::serialized_field = "RegressionLevelName"]
regression_level_name: NameProperty,
#[paramacro::serialized_field = "bStartFromRegressionPoint"]
start_from_regression_point: BoolProperty,
#[paramacro::serialized_field = "ReleasedCheckpoints"]
released_checkpoints: SetProperty,
#[paramacro::serialized_field = "SuspendTransform"]
suspend_transform: TransformStruct,
#[paramacro::serialized_field = "CharacterPersistentDataList"]
character_persistent_data_list: MapProperty,
#[paramacro::serialized_field = "BossStates"]
boss_states: MapProperty,
#[paramacro::serialized_field = "NPCStates"]
npc_states: MapProperty,
#[paramacro::serialized_field = "ReadDialogues"]
read_dialogues: MapProperty,
#[paramacro::serialized_field = "ReadDialogueChains"]
read_dialogue_chains: MapProperty,
#[paramacro::serialized_field = "SaveGameName"]
save_game_name: StrProperty,
#[paramacro::serialized_field = "bUseSaveSlot"]
use_save_slot: BoolProperty,
}

View file

@ -10,6 +10,9 @@ fn cc() -> BinResult<Vec<GenericProperty>> {
loop {
if let Ok(str) = GenericProperty::read_options(reader, endian, ()) {
if str.property_name == "None" {
break;
}
result.push(str);
} else {
break;
@ -20,32 +23,36 @@ fn cc() -> BinResult<Vec<GenericProperty>> {
#[binrw]
#[derive(Debug)]
#[br(import { magic: &str, name: &str })]
#[br(import { magic: &str, key_type: &KeyType })]
pub enum SetValue {
// NOTE: the name checking is just a temporary workaround
#[br(pre_assert("StructProperty" == magic && name != "OpenedStrongBoxIds" && name != "AcquiredItemBoxIds"))]
#[br(pre_assert("StructProperty" == magic && *key_type != KeyType::Unknown && *key_type != KeyType::EnumAgain))]
Struct {
#[br(parse_with = cc)]
fields: Vec<GenericProperty>,
},
#[br(pre_assert("StringProperty" == magic || "NameProperty" == magic))]
String(MapSubStrProperty),
#[br(pre_assert("StructProperty" == magic && name == "AcquiredItemBoxIds"))]
#[br(pre_assert("StructProperty" == magic && *key_type == KeyType::Unknown))]
Unknown { unk: [u8; 736] },
#[br(pre_assert("StructProperty" == magic && name == "OpenedStrongBoxIds"))]
#[br(pre_assert("StructProperty" == magic && *key_type == KeyType::EnumAgain))]
Unknown2 { unk: [u8; 64] },
}
#[binrw]
#[derive(Debug)]
#[br(import(value_type: &str, key_type: &str))]
#[br(import(value_type: &str, key_type: &KeyType))]
pub struct SetEntry {
#[br(args { magic: value_type, name: key_type })]
#[br(args { magic: value_type, key_type })]
#[br(dbg)]
pub key: SetValue,
}
#[binrw::parser(reader, endian)]
fn custom_parser(size_in_bytes: u32, value_type: &str, key_type: &str) -> BinResult<Vec<SetEntry>> {
fn custom_parser(
size_in_bytes: u32,
value_type: &str,
key_type: &KeyType,
) -> BinResult<Vec<SetEntry>> {
let mut result = Vec::<SetEntry>::new();
let mut current = reader.stream_position()?;
@ -64,22 +71,34 @@ fn custom_parser(size_in_bytes: u32, value_type: &str, key_type: &str) -> BinRes
#[binrw]
#[derive(Debug)]
#[br(import(name: &str))]
pub struct SetProperty {
pub size_in_bytes: u32,
#[brw(pad_before = 4)]
#[br(parse_with = read_string_with_length)]
#[bw(write_with = write_string_with_length)]
#[br(dbg)]
pub key_name: String,
#[brw(pad_before = 5)]
#[br(dbg)]
pub key_type: KeyType,
#[br(parse_with = custom_parser, args(size_in_bytes, &key_name, name))]
#[br(parse_with = custom_parser, args(size_in_bytes, &key_name, &key_type))]
pub entries: Vec<SetEntry>,
}
impl crate::structs::PropertyBase for SetProperty {
fn type_name() -> &'static str {
"SetProperty"
}
fn size_in_bytes(&self) -> u32 {
// todo
0
}
}
#[cfg(test)]
mod tests {
use super::*;

36
src/slot.rs Normal file
View file

@ -0,0 +1,36 @@
use crate::{
bool_property::BoolProperty, da_load_option::DALoadOptionStruct, datetime::DateTimeStruct,
float_property::FloatProperty, int_property::IntProperty, map_property::MapProperty,
name_property::NameProperty, save_slot_info::SaveSlotInfoStruct, str_property::StrProperty,
};
#[paramacro::serialized_struct("Transform")]
#[derive(Debug)]
pub struct Slot {
#[paramacro::serialized_field = "SavedDataVersion"]
version: IntProperty,
#[paramacro::serialized_field = "bDemoVersion"]
demo: BoolProperty,
#[paramacro::serialized_field = "CreatedTimeStamp"]
created_timestamp: DateTimeStruct,
#[paramacro::serialized_field = "PlayTime"]
playtime: FloatProperty,
#[paramacro::serialized_field = "RegisteredName"]
name: StrProperty,
#[paramacro::serialized_field = "LoadOption"]
load_option: DALoadOptionStruct,
#[paramacro::serialized_field = "DistrictTag"]
district_tag: NameProperty,
#[paramacro::serialized_field = "CycleCount"]
cycle_count: IntProperty,
#[paramacro::serialized_field = "SlotInfo"]
slot_info: SaveSlotInfoStruct,
}

View file

@ -1,5 +1,5 @@
use binrw::{BinRead, BinWrite};
use ireko::TaggedSerialization;
use ireko::{GenericTaggedObject, TaggedSerialization};
use std::fs::read;
use std::io::Cursor;
use std::path::PathBuf;
@ -13,8 +13,8 @@ fn roundtrip_localprofile() {
let mut data = read(d).unwrap();
let mut cursor = Cursor::new(&mut data);
let local_profile = TaggedSerialization::read_le(&mut cursor).unwrap();
let tagged_object = &local_profile.objs[0];
let local_profile = TaggedSerialization::<GenericTaggedObject>::read_le(&mut cursor).unwrap();
let tagged_object = &local_profile.objs;
assert_eq!(tagged_object.size_in_bytes, 339);
tagged_object.entry("SavedDataVersion").unwrap();
@ -41,8 +41,8 @@ fn roundtrip_slot() {
let mut data = read(d).unwrap();
let mut cursor = Cursor::new(&mut data);
let local_profile = TaggedSerialization::read_le(&mut cursor).unwrap();
let tagged_object = &local_profile.objs[0];
let local_profile = TaggedSerialization::<GenericTaggedObject>::read_le(&mut cursor).unwrap();
let tagged_object = &local_profile.objs;
assert_eq!(tagged_object.size_in_bytes, 900);
tagged_object.entry("PlayTime").unwrap();