Various set and map fixes, add structured object support
This commit is contained in:
parent
0b461c5b86
commit
d622799cfa
8 changed files with 245 additions and 48 deletions
|
@ -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(())
|
||||
}
|
||||
|
|
63
src/lib.rs
63
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<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
23
src/localprofile.rs
Normal 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,
|
||||
}
|
|
@ -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
101
src/persistent.rs
Normal 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,
|
||||
}
|
|
@ -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
36
src/slot.rs
Normal 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,
|
||||
}
|
|
@ -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();
|
||||
|
|
Loading…
Add table
Reference in a new issue