diff --git a/src/bin/ireko.rs b/src/bin/ireko.rs index 255b51c..afe4b67 100644 --- a/src/bin/ireko.rs +++ b/src/bin/ireko.rs @@ -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> { let mut data = Cursor::new(std::fs::read(&args[1])?); - let compressed = CompressedSaveFile::read_le(&mut data)?; + let compressed = CompressedSaveFile::::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(()) } diff --git a/src/lib.rs b/src/lib.rs index 904af04..52fc0d2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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) -> BinResult<()> { Ok(()) } -#[binrw::parser(reader, endian)] -fn custom_parser(size_in_bytes: u32) -> BinResult> { - let mut result = Vec::::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, } -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 +where + for<'a> T: BinRead = ()> + 'a, + for<'a> T: BinWrite = ()> + 'a, +{ pub size_in_bytes: u32, - #[br(parse_with = custom_parser, args(size_in_bytes))] - pub objs: Vec, + 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) -> BinResult { +fn read_tagged_data(data_blocks: &Vec) -> BinResult> +where + for<'a> T: BinRead = ()> + 'a, + for<'a> T: BinWrite = ()> + 'a, +{ let data_vecs: Vec> = 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::::read_le(&mut cursor) } #[binrw] @@ -210,11 +207,15 @@ pub struct CompressedBlock { #[binrw] #[derive(Debug)] -pub struct CompressedSaveFile { +pub struct CompressedSaveFile +where + for<'a> T: BinRead = ()> + 'a, + for<'a> T: BinWrite = ()> + 'a, +{ #[br(parse_with = until_eof)] #[br(temp)] #[bw(ignore)] pub data: Vec, #[br(parse_with = read_tagged_data, args(&data))] - pub value: TaggedSerialization, + pub value: TaggedSerialization, } diff --git a/src/localprofile.rs b/src/localprofile.rs new file mode 100644 index 0000000..b6a3b49 --- /dev/null +++ b/src/localprofile.rs @@ -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, +} diff --git a/src/map_property.rs b/src/map_property.rs index c0e4e79..a841ef0 100644 --- a/src/map_property.rs +++ b/src/map_property.rs @@ -13,6 +13,9 @@ fn cc() -> BinResult> { 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, } #[binrw] diff --git a/src/persistent.rs b/src/persistent.rs new file mode 100644 index 0000000..c779a4b --- /dev/null +++ b/src/persistent.rs @@ -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, +} diff --git a/src/set_property.rs b/src/set_property.rs index 60bdab6..60ad730 100644 --- a/src/set_property.rs +++ b/src/set_property.rs @@ -10,6 +10,9 @@ fn cc() -> BinResult> { 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> { #[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, }, #[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> { +fn custom_parser( + size_in_bytes: u32, + value_type: &str, + key_type: &KeyType, +) -> BinResult> { let mut result = Vec::::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, } +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::*; diff --git a/src/slot.rs b/src/slot.rs new file mode 100644 index 0000000..0b9b53a --- /dev/null +++ b/src/slot.rs @@ -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, +} diff --git a/tests/roundtrip.rs b/tests/roundtrip.rs index adc0fc6..58fd0b4 100644 --- a/tests/roundtrip.rs +++ b/tests/roundtrip.rs @@ -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::::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::::read_le(&mut cursor).unwrap(); + let tagged_object = &local_profile.objs; assert_eq!(tagged_object.size_in_bytes, 900); tagged_object.entry("PlayTime").unwrap();