From f304304d1900f404dfe8f1ef939165b4f8a0ae6e Mon Sep 17 00:00:00 2001 From: Joshua Goins Date: Sun, 23 Feb 2025 15:25:50 -0500 Subject: [PATCH] Simplify string reading A lot of them are just u32 length + byte string, and can be moved into a common parsing function. --- src/array_property.rs | 35 +++++------------- src/bin/ireko.rs | 2 +- src/bool_property.rs | 2 +- src/common.rs | 53 +++++++++++++++++++++++++++ src/lib.rs | 6 +--- src/map_property.rs | 82 +++++++++++++----------------------------- src/set_property.rs | 22 +++--------- src/struct_property.rs | 18 ++++------ src/structs.rs | 9 +---- 9 files changed, 102 insertions(+), 127 deletions(-) create mode 100644 src/common.rs diff --git a/src/array_property.rs b/src/array_property.rs index 7effd71..7d36da6 100644 --- a/src/array_property.rs +++ b/src/array_property.rs @@ -1,5 +1,6 @@ -use crate::map_property::{MabSubProperty}; -use binrw::{binrw, BinRead, BinResult}; +use crate::common::read_string_with_length; +use crate::map_property::MabSubProperty; +use binrw::{BinRead, BinResult, binrw}; #[binrw::parser(reader, endian)] fn custom_parser(size_in_bytes: u32, value_type: &str) -> BinResult> { @@ -15,13 +16,11 @@ fn custom_parser(size_in_bytes: u32, value_type: &str) -> BinResult | String::from_utf8(x).unwrap().trim_matches(char::from(0)).to_string())] - pub key_name: String, #[br(pad_before = 1)] @@ -60,20 +52,9 @@ mod tests { fn simple_array() { // From Slot.sav let data = [ - 0x12, 0x00, 0x00, - 0x00, 0x00, 0x00, - 0x00, 0x00, 0x0c, - 0x00, 0x00, 0x00, - 0x53, 0x74, 0x72, - 0x50, 0x72, 0x6f, - 0x70, 0x65, 0x72, - 0x74, 0x79, 0x00, - 0x00, 0x01, 0x00, - 0x00, 0x00, 0x0a, - 0x00, 0x00, 0x00, - 0x72, 0x65, 0x64, - 0x73, 0x74, 0x72, - 0x61, 0x74, 0x65, + 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x53, 0x74, + 0x72, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x0a, 0x00, 0x00, 0x00, 0x72, 0x65, 0x64, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x00, ]; let mut cursor = Cursor::new(data); @@ -83,6 +64,6 @@ mod tests { let String(value_property) = &decoded.entries.first().unwrap().key else { panic!("StrProperty!") }; - assert_eq!(value_property.name, "redstrate"); + assert_eq!(value_property.value, "redstrate"); } } diff --git a/src/bin/ireko.rs b/src/bin/ireko.rs index a4e3ac2..962ba6e 100644 --- a/src/bin/ireko.rs +++ b/src/bin/ireko.rs @@ -1,6 +1,6 @@ use binrw::BinRead; -use flate2::bufread::ZlibDecoder; use flate2::Decompress; +use flate2::bufread::ZlibDecoder; use ireko::{CompressedSaveFile, TaggedSerialization}; use std::env; use std::io::{Cursor, Read}; diff --git a/src/bool_property.rs b/src/bool_property.rs index b85c135..3627563 100644 --- a/src/bool_property.rs +++ b/src/bool_property.rs @@ -1,4 +1,4 @@ -use crate::read_bool_from; +use crate::common::read_bool_from; use binrw::binrw; #[binrw] diff --git a/src/common.rs b/src/common.rs new file mode 100644 index 0000000..be6462d --- /dev/null +++ b/src/common.rs @@ -0,0 +1,53 @@ +use binrw::{BinRead, BinReaderExt, BinResult}; + +pub(crate) fn read_bool_from + PartialEq>(x: T) -> bool { + x == T::from(1u8) +} + +#[binrw::parser(reader, endian)] +pub(crate) fn read_string_with_length() -> BinResult { + // last byte is the null terminator which Rust ignores + let length = u32::read_le(reader)? as usize - 1; + let mut bytes: Vec = vec![0u8; length]; + // TODO: there was to be way to read this all in one go? + for i in 0..length { + bytes[i] = u8::read_le(reader)?; + } + u8::read_le(reader)?; // read null terminator + String::from_utf8(bytes).or(Err(binrw::Error::AssertFail { + pos: 0, + message: "dummy".to_string(), + })) +} + +#[cfg(test)] +mod tests { + use super::*; + use binrw::{BinRead, binread}; + use std::io::Cursor; + use std::string::String; + + #[test] + fn read_bool_u8() { + assert!(!read_bool_from::(0u8)); + assert!(read_bool_from::(1u8)); + } + + #[test] + fn read_string() { + // From LocalProfile.sav i think + let data = [ + 0x0a, 0x00, 0x00, 0x00, 0x72, 0x65, 0x64, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x00, + ]; + + #[binread] + struct TestStruct { + #[br(parse_with = read_string_with_length)] + value: String, + } + + let mut cursor = Cursor::new(data); + let decoded = TestStruct::read_le(&mut cursor).unwrap(); + assert_eq!(decoded.value, "redstrate"); + } +} diff --git a/src/lib.rs b/src/lib.rs index e221efd..adb2441 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ pub mod array_property; pub mod bool_property; +mod common; pub mod float_property; pub mod int_property; pub mod map_property; @@ -22,10 +23,6 @@ use crate::str_property::StrProperty; use crate::struct_property::StructProperty; use binrw::binrw; -pub(crate) fn read_bool_from + PartialEq>(x: T) -> bool { - x == T::from(1u8) -} - #[binrw] #[derive(Debug)] pub enum Property { @@ -77,7 +74,6 @@ pub enum StringBasedProperty { pub struct Entry { #[br(temp)] #[bw(ignore)] - pub name_length: u32, #[br(count = name_length)] #[bw(map = |x : &String | x.as_bytes())] diff --git a/src/map_property.rs b/src/map_property.rs index 0c24621..c7d6e72 100644 --- a/src/map_property.rs +++ b/src/map_property.rs @@ -1,8 +1,8 @@ -use crate::read_bool_from; +use crate::common::{read_bool_from, read_string_with_length}; use crate::struct_property::Struct; use crate::structs::PrimaryAssetNameProperty; use binrw::helpers::until_exclusive; -use binrw::{binrw, BinRead, BinResult}; +use binrw::{BinRead, BinResult, binrw}; // A struct without a name #[binrw] @@ -24,24 +24,14 @@ pub struct MapSubStructProperty { #[binrw] #[derive(Debug)] pub struct StructMaybeKey { - #[br(temp)] + #[br(parse_with = read_string_with_length)] #[bw(ignore)] - pub unk_name_length: u32, - #[br(count = unk_name_length)] - #[bw(map = |x : &String | x.as_bytes())] - #[br(map = | x: Vec | String::from_utf8(x).unwrap().trim_matches(char::from(0)).to_string())] pub unk_name: String, - #[br(temp)] + #[br(pad_after = 8, parse_with = read_string_with_length)] #[bw(ignore)] - pub unk_type_length: u32, - #[br(count = unk_type_length)] - #[bw(map = |x : &String | x.as_bytes())] - #[br(map = | x: Vec | String::from_utf8(x).unwrap().trim_matches(char::from(0)).to_string())] - #[br(pad_after = 8)] // nothingness and then length for the struct pub unk_type: String, - #[br(pad_before = 4)] // type length pub r#struct: Struct, } @@ -55,25 +45,17 @@ pub struct MapSubFloatProperty { #[binrw] #[derive(Debug)] pub struct MapSubNameProperty { - #[br(temp)] + #[br(parse_with = read_string_with_length)] #[bw(ignore)] - pub sub_property_name_length: u32, - #[br(count = sub_property_name_length)] - #[bw(map = |x : &String | x.as_bytes())] - #[br(map = | x: Vec | String::from_utf8(x).unwrap().trim_matches(char::from(0)).to_string())] - pub sub_property_name: String, + pub value: String, } #[binrw] #[derive(Debug)] pub struct MapSubStrProperty { - #[br(temp)] + #[br(parse_with = read_string_with_length)] #[bw(ignore)] - pub name_length: u32, - #[br(count = name_length)] - #[bw(map = |x : &String | x.as_bytes())] - #[br(map = | x: Vec | String::from_utf8(x).unwrap().trim_matches(char::from(0)).to_string())] - pub name: String, + pub value: String, } #[binrw] @@ -93,11 +75,9 @@ pub struct MapSubIntProperty { #[binrw] #[derive(Debug)] pub struct MapSubEnumProperty { - pub enum_length: u32, - #[br(count = enum_length)] - #[bw(map = |x : &String | x.as_bytes())] - #[br(map = | x: Vec | String::from_utf8(x).unwrap().trim_matches(char::from(0)).to_string())] - pub r#enum: String, + #[br(parse_with = read_string_with_length)] + #[bw(ignore)] + pub value: String, } #[binrw] @@ -148,11 +128,9 @@ pub struct SomeID2Struct { #[binrw] #[derive(Debug)] pub struct StringMapKey { - pub enum_length: u32, - #[br(count = enum_length)] - #[bw(map = |x : &String | x.as_bytes())] - #[br(map = | x: Vec | String::from_utf8(x).unwrap().trim_matches(char::from(0)).to_string())] - pub r#enum: String, + #[br(parse_with = read_string_with_length)] + #[bw(ignore)] + pub value: String, } #[binrw] @@ -180,18 +158,15 @@ pub enum MapKeyProperty { #[br(import(key_type: &KeyType, value_type: &str))] pub struct MapEntry { #[br(args { magic: key_type})] - pub key: MapKeyProperty, #[br(args { magic: value_type })] - pub value: MabSubProperty, } #[binrw] #[brw(repr = u32)] -#[derive(Debug)] -#[derive(PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone)] pub enum KeyType { String = 0x1, GUID = 0x3, @@ -203,7 +178,11 @@ pub enum KeyType { } #[binrw::parser(reader, endian)] -fn custom_parser(size_in_bytes: u32, key_type: &KeyType, value_type: &str) -> BinResult> { +fn custom_parser( + size_in_bytes: u32, + key_type: &KeyType, + value_type: &str, +) -> BinResult> { let mut result = Vec::::new(); let mut current = reader.stream_position().unwrap(); @@ -220,28 +199,17 @@ fn custom_parser(size_in_bytes: u32, key_type: &KeyType, value_type: &str) -> Bi #[derive(Debug)] pub struct MapProperty { // Plus this int - pub size_in_bytes: u32, - #[br(temp)] + #[br(pad_before = 4, parse_with = read_string_with_length)] #[bw(ignore)] - #[br(pad_before = 4)] - pub key_name_length: u32, - #[br(count = key_name_length)] - #[bw(map = |x : &String | x.as_bytes())] - #[br(map = | x: Vec | String::from_utf8(x).unwrap().trim_matches(char::from(0)).to_string())] pub key_name: String, - #[br(temp)] + #[br(parse_with = read_string_with_length)] #[bw(ignore)] - pub value_name_length: u32, - #[br(count = value_name_length)] - #[bw(map = |x : &String | x.as_bytes())] - #[br(map = | x: Vec | String::from_utf8(x).unwrap().trim_matches(char::from(0)).to_string())] pub value_name: String, #[br(pad_before = 5)] - pub key_type: KeyType, #[br(parse_with = custom_parser, args(size_in_bytes, &key_type, &value_name))] @@ -278,8 +246,8 @@ mod tests { let String(value_property) = &decoded.entries.first().unwrap().value else { panic!("StrProperty!") }; - assert_eq!(key_property.r#enum, "AR0XJGFWA6HNIQ1AAUJ9UR828"); - assert_eq!(value_property.name, "NAME 1"); + assert_eq!(key_property.value, "AR0XJGFWA6HNIQ1AAUJ9UR828"); + assert_eq!(value_property.value, "NAME 1"); } #[test] @@ -315,7 +283,7 @@ mod tests { let Int(value_property) = &decoded.entries.first().unwrap().value else { panic!("Int!") }; - assert_eq!(key_property.r#enum, "SelectedMachine"); + assert_eq!(key_property.value, "SelectedMachine"); assert_eq!(value_property.value, 2); // TODO: test the rest of the values } diff --git a/src/set_property.rs b/src/set_property.rs index f813866..cbe3c4f 100644 --- a/src/set_property.rs +++ b/src/set_property.rs @@ -1,27 +1,19 @@ use crate::StringBasedProperty; +use crate::common::read_string_with_length; use binrw::binrw; #[binrw] #[derive(Debug)] pub struct SetEntry { - #[br(temp)] + #[br(parse_with = read_string_with_length)] #[bw(ignore)] - pub unk_name_length: u32, - #[br(count = unk_name_length)] - #[bw(map = |x : &String | x.as_bytes())] - #[br(map = | x: Vec | String::from_utf8(x).unwrap().trim_matches(char::from(0)).to_string())] pub unk_name: String, - #[br(temp)] + #[br(parse_with = read_string_with_length)] #[bw(ignore)] - pub unk_type_length: u32, - #[br(count = unk_type_length)] - #[bw(map = |x : &String | x.as_bytes())] - #[br(map = | x: Vec | String::from_utf8(x).unwrap().trim_matches(char::from(0)).to_string())] pub unk_type: String, #[br(args { magic: &unk_type })] - pub key: StringBasedProperty, } @@ -29,13 +21,9 @@ pub struct SetEntry { #[derive(Debug)] pub struct SetProperty { pub unk: u32, - #[br(temp)] + + #[br(pad_before = 4, parse_with = read_string_with_length)] #[bw(ignore)] - #[br(pad_before = 4)] - pub key_name_length: u32, - #[br(count = key_name_length)] - #[bw(map = |x : &String | x.as_bytes())] - #[br(map = | x: Vec | String::from_utf8(x).unwrap().trim_matches(char::from(0)).to_string())] pub key_name: String, #[br(pad_before = 5)] diff --git a/src/struct_property.rs b/src/struct_property.rs index 346f1f9..22b1257 100644 --- a/src/struct_property.rs +++ b/src/struct_property.rs @@ -1,4 +1,9 @@ -use crate::structs::{CarryCountProperty, DAAssembleIdDataStruct, DABuildDataStruct, DACharacterCommonStatusStruct, DALoadOptionStruct, DAMachineColoringDataStruct, DAModuleColorStruct, DAModuleItemDataStruct, DateTimeStruct, GuidStruct, LinearColorStruct, ParamsStruct, PrimaryAssetIdStruct, PrimaryAssetNameProperty, SaveSlotInfoStruct}; +use crate::structs::{ + CarryCountProperty, DAAssembleIdDataStruct, DABuildDataStruct, DACharacterCommonStatusStruct, + DALoadOptionStruct, DAMachineColoringDataStruct, DAModuleColorStruct, DAModuleItemDataStruct, + DateTimeStruct, GuidStruct, LinearColorStruct, ParamsStruct, PrimaryAssetIdStruct, + PrimaryAssetNameProperty, SaveSlotInfoStruct, +}; use binrw::binrw; #[binrw] @@ -17,11 +22,9 @@ pub enum Struct { #[br(magic = b"PrimaryAssetType\0")] PrimaryAssetType { #[br(pad_before = 17)] - name: PrimaryAssetNameProperty, #[br(pad_before = 9)] // "None" and it's length in bytes plus the null terminator #[br(pad_after = 9)] // ditto - primary_asset_name: PrimaryAssetNameProperty, }, #[br(magic = b"PrimaryAssetId\0")] @@ -42,7 +45,6 @@ pub enum Struct { LinearColor(LinearColorStruct), #[br(magic = b"CarryCount\0")] CarryCount { - carry_count: CarryCountProperty, #[br(pad_before = 15)] // "StoreCount" + 4 bytes for length + 1 byte for endofstring @@ -51,7 +53,6 @@ pub enum Struct { }, #[br(magic = b"Map\0")] Map { - #[br(pad_after = 9)] // "None" + 1 byte for endofstring + 4 bytes for length map: CarryCountProperty, }, @@ -63,23 +64,18 @@ pub enum Struct { #[br(pad_after = 9)] // "None" and it's length in bytes plus the null terminator name: PrimaryAssetNameProperty, - #[br(pad_after = 9)] // "None" and it's length in bytes plus the null terminator primary_asset_name: PrimaryAssetNameProperty, - data: [u8; 137], }, #[br(magic = b"Set\0")] Set { - #[br(pad_after = 9)] // "None" + 1 byte for endofstring + 4 bytes for length set: CarryCountProperty, }, #[br(magic = b"ItemSlots\0")] - ItemSlots { - unk: [u8; 2125] - }, + ItemSlots { unk: [u8; 2125] }, } #[binrw] diff --git a/src/structs.rs b/src/structs.rs index 44ef41b..5a1a891 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -38,24 +38,20 @@ pub struct ParamsStruct { pub struct PrimaryAssetNameProperty { #[br(temp)] #[bw(ignore)] - pub property_name_length: u32, #[br(count = property_name_length)] #[bw(map = |x : &String | x.as_bytes())] #[br(map = | x: Vec | String::from_utf8(x).unwrap().trim_matches(char::from(0)).to_string())] - pub property_name: String, #[br(temp)] #[bw(ignore)] #[br(if(property_name != "None"))] - pub type_length: u32, #[br(count = type_length)] #[bw(map = |x : &String | x.as_bytes())] #[br(map = | x: Vec | String::from_utf8(x).unwrap().trim_matches(char::from(0)).to_string())] #[br(if(property_name != "None"))] - pub type_name: String, #[br(if(property_name != "None"))] @@ -64,18 +60,15 @@ pub struct PrimaryAssetNameProperty { pub key: Option>, } - #[binrw] #[derive(Debug)] pub struct CarryCountProperty { #[br(temp)] #[bw(ignore)] - pub property_name_length: u32, #[br(count = property_name_length)] #[bw(map = |x : &String | x.as_bytes())] #[br(map = | x: Vec | String::from_utf8(x).unwrap().trim_matches(char::from(0)).to_string())] - pub property_name: String, #[br(args { magic: &property_name})] @@ -94,7 +87,7 @@ pub struct PrimaryAssetIdStruct { #[derive(Debug)] pub struct DAModuleItemDataStruct { #[br(pad_before = 17)] - pub module_level: PrimaryAssetNameProperty + pub module_level: PrimaryAssetNameProperty, } #[binrw]