Simplify string reading

A lot of them are just u32 length + byte string, and can be moved into
a common parsing function.
This commit is contained in:
Joshua Goins 2025-02-23 15:25:50 -05:00
parent a4615b29ce
commit f304304d19
9 changed files with 102 additions and 127 deletions

View file

@ -1,5 +1,6 @@
use crate::map_property::{MabSubProperty}; use crate::common::read_string_with_length;
use binrw::{binrw, BinRead, BinResult}; use crate::map_property::MabSubProperty;
use binrw::{BinRead, BinResult, binrw};
#[binrw::parser(reader, endian)] #[binrw::parser(reader, endian)]
fn custom_parser(size_in_bytes: u32, value_type: &str) -> BinResult<Vec<ArrayEntry>> { fn custom_parser(size_in_bytes: u32, value_type: &str) -> BinResult<Vec<ArrayEntry>> {
@ -15,13 +16,11 @@ fn custom_parser(size_in_bytes: u32, value_type: &str) -> BinResult<Vec<ArrayEnt
Ok(result) Ok(result)
} }
#[binrw] #[binrw]
#[derive(Debug)] #[derive(Debug)]
#[br(import(value_type: &str))] #[br(import(value_type: &str))]
pub struct ArrayEntry { pub struct ArrayEntry {
#[br(args { magic: &value_type })] #[br(args { magic: &value_type })]
pub key: MabSubProperty, pub key: MabSubProperty,
} }
@ -29,17 +28,10 @@ pub struct ArrayEntry {
#[derive(Debug)] #[derive(Debug)]
pub struct ArrayProperty { pub struct ArrayProperty {
// Plus this int // Plus this int
pub size_in_bytes: u32, pub size_in_bytes: u32,
#[br(temp)] #[br(pad_before = 4, parse_with = read_string_with_length)]
#[bw(ignore)] #[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<u8> | String::from_utf8(x).unwrap().trim_matches(char::from(0)).to_string())]
pub key_name: String, pub key_name: String,
#[br(pad_before = 1)] #[br(pad_before = 1)]
@ -60,20 +52,9 @@ mod tests {
fn simple_array() { fn simple_array() {
// From Slot.sav // From Slot.sav
let data = [ let data = [
0x12, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x53, 0x74,
0x00, 0x00, 0x00, 0x72, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x00, 0x00, 0x01, 0x00, 0x00,
0x00, 0x00, 0x0c, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x72, 0x65, 0x64, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65,
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, 0x00,
]; ];
let mut cursor = Cursor::new(data); let mut cursor = Cursor::new(data);
@ -83,6 +64,6 @@ mod tests {
let String(value_property) = &decoded.entries.first().unwrap().key else { let String(value_property) = &decoded.entries.first().unwrap().key else {
panic!("StrProperty!") panic!("StrProperty!")
}; };
assert_eq!(value_property.name, "redstrate"); assert_eq!(value_property.value, "redstrate");
} }
} }

View file

@ -1,6 +1,6 @@
use binrw::BinRead; use binrw::BinRead;
use flate2::bufread::ZlibDecoder;
use flate2::Decompress; use flate2::Decompress;
use flate2::bufread::ZlibDecoder;
use ireko::{CompressedSaveFile, TaggedSerialization}; use ireko::{CompressedSaveFile, TaggedSerialization};
use std::env; use std::env;
use std::io::{Cursor, Read}; use std::io::{Cursor, Read};

View file

@ -1,4 +1,4 @@
use crate::read_bool_from; use crate::common::read_bool_from;
use binrw::binrw; use binrw::binrw;
#[binrw] #[binrw]

53
src/common.rs Normal file
View file

@ -0,0 +1,53 @@
use binrw::{BinRead, BinReaderExt, BinResult};
pub(crate) fn read_bool_from<T: From<u8> + PartialEq>(x: T) -> bool {
x == T::from(1u8)
}
#[binrw::parser(reader, endian)]
pub(crate) fn read_string_with_length() -> BinResult<String> {
// last byte is the null terminator which Rust ignores
let length = u32::read_le(reader)? as usize - 1;
let mut bytes: Vec<u8> = 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::<u8>(0u8));
assert!(read_bool_from::<u8>(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");
}
}

View file

@ -1,5 +1,6 @@
pub mod array_property; pub mod array_property;
pub mod bool_property; pub mod bool_property;
mod common;
pub mod float_property; pub mod float_property;
pub mod int_property; pub mod int_property;
pub mod map_property; pub mod map_property;
@ -22,10 +23,6 @@ use crate::str_property::StrProperty;
use crate::struct_property::StructProperty; use crate::struct_property::StructProperty;
use binrw::binrw; use binrw::binrw;
pub(crate) fn read_bool_from<T: From<u8> + PartialEq>(x: T) -> bool {
x == T::from(1u8)
}
#[binrw] #[binrw]
#[derive(Debug)] #[derive(Debug)]
pub enum Property { pub enum Property {
@ -77,7 +74,6 @@ pub enum StringBasedProperty {
pub struct Entry { pub struct Entry {
#[br(temp)] #[br(temp)]
#[bw(ignore)] #[bw(ignore)]
pub name_length: u32, pub name_length: u32,
#[br(count = name_length)] #[br(count = name_length)]
#[bw(map = |x : &String | x.as_bytes())] #[bw(map = |x : &String | x.as_bytes())]

View file

@ -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::struct_property::Struct;
use crate::structs::PrimaryAssetNameProperty; use crate::structs::PrimaryAssetNameProperty;
use binrw::helpers::until_exclusive; use binrw::helpers::until_exclusive;
use binrw::{binrw, BinRead, BinResult}; use binrw::{BinRead, BinResult, binrw};
// A struct without a name // A struct without a name
#[binrw] #[binrw]
@ -24,24 +24,14 @@ pub struct MapSubStructProperty {
#[binrw] #[binrw]
#[derive(Debug)] #[derive(Debug)]
pub struct StructMaybeKey { pub struct StructMaybeKey {
#[br(temp)] #[br(parse_with = read_string_with_length)]
#[bw(ignore)] #[bw(ignore)]
pub unk_name_length: u32,
#[br(count = unk_name_length)]
#[bw(map = |x : &String | x.as_bytes())]
#[br(map = | x: Vec<u8> | String::from_utf8(x).unwrap().trim_matches(char::from(0)).to_string())]
pub unk_name: String, pub unk_name: String,
#[br(temp)] #[br(pad_after = 8, parse_with = read_string_with_length)]
#[bw(ignore)] #[bw(ignore)]
pub unk_type_length: u32,
#[br(count = unk_type_length)]
#[bw(map = |x : &String | x.as_bytes())]
#[br(map = | x: Vec<u8> | 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, pub unk_type: String,
#[br(pad_before = 4)] // type length #[br(pad_before = 4)] // type length
pub r#struct: Struct, pub r#struct: Struct,
} }
@ -55,25 +45,17 @@ pub struct MapSubFloatProperty {
#[binrw] #[binrw]
#[derive(Debug)] #[derive(Debug)]
pub struct MapSubNameProperty { pub struct MapSubNameProperty {
#[br(temp)] #[br(parse_with = read_string_with_length)]
#[bw(ignore)] #[bw(ignore)]
pub sub_property_name_length: u32, pub value: String,
#[br(count = sub_property_name_length)]
#[bw(map = |x : &String | x.as_bytes())]
#[br(map = | x: Vec<u8> | String::from_utf8(x).unwrap().trim_matches(char::from(0)).to_string())]
pub sub_property_name: String,
} }
#[binrw] #[binrw]
#[derive(Debug)] #[derive(Debug)]
pub struct MapSubStrProperty { pub struct MapSubStrProperty {
#[br(temp)] #[br(parse_with = read_string_with_length)]
#[bw(ignore)] #[bw(ignore)]
pub name_length: u32, pub value: String,
#[br(count = name_length)]
#[bw(map = |x : &String | x.as_bytes())]
#[br(map = | x: Vec<u8> | String::from_utf8(x).unwrap().trim_matches(char::from(0)).to_string())]
pub name: String,
} }
#[binrw] #[binrw]
@ -93,11 +75,9 @@ pub struct MapSubIntProperty {
#[binrw] #[binrw]
#[derive(Debug)] #[derive(Debug)]
pub struct MapSubEnumProperty { pub struct MapSubEnumProperty {
pub enum_length: u32, #[br(parse_with = read_string_with_length)]
#[br(count = enum_length)] #[bw(ignore)]
#[bw(map = |x : &String | x.as_bytes())] pub value: String,
#[br(map = | x: Vec<u8> | String::from_utf8(x).unwrap().trim_matches(char::from(0)).to_string())]
pub r#enum: String,
} }
#[binrw] #[binrw]
@ -148,11 +128,9 @@ pub struct SomeID2Struct {
#[binrw] #[binrw]
#[derive(Debug)] #[derive(Debug)]
pub struct StringMapKey { pub struct StringMapKey {
pub enum_length: u32, #[br(parse_with = read_string_with_length)]
#[br(count = enum_length)] #[bw(ignore)]
#[bw(map = |x : &String | x.as_bytes())] pub value: String,
#[br(map = | x: Vec<u8> | String::from_utf8(x).unwrap().trim_matches(char::from(0)).to_string())]
pub r#enum: String,
} }
#[binrw] #[binrw]
@ -180,18 +158,15 @@ pub enum MapKeyProperty {
#[br(import(key_type: &KeyType, value_type: &str))] #[br(import(key_type: &KeyType, value_type: &str))]
pub struct MapEntry { pub struct MapEntry {
#[br(args { magic: key_type})] #[br(args { magic: key_type})]
pub key: MapKeyProperty, pub key: MapKeyProperty,
#[br(args { magic: value_type })] #[br(args { magic: value_type })]
pub value: MabSubProperty, pub value: MabSubProperty,
} }
#[binrw] #[binrw]
#[brw(repr = u32)] #[brw(repr = u32)]
#[derive(Debug)] #[derive(Debug, PartialEq, Clone)]
#[derive(PartialEq, Clone)]
pub enum KeyType { pub enum KeyType {
String = 0x1, String = 0x1,
GUID = 0x3, GUID = 0x3,
@ -203,7 +178,11 @@ pub enum KeyType {
} }
#[binrw::parser(reader, endian)] #[binrw::parser(reader, endian)]
fn custom_parser(size_in_bytes: u32, key_type: &KeyType, value_type: &str) -> BinResult<Vec<MapEntry>> { fn custom_parser(
size_in_bytes: u32,
key_type: &KeyType,
value_type: &str,
) -> BinResult<Vec<MapEntry>> {
let mut result = Vec::<MapEntry>::new(); let mut result = Vec::<MapEntry>::new();
let mut current = reader.stream_position().unwrap(); 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)] #[derive(Debug)]
pub struct MapProperty { pub struct MapProperty {
// Plus this int // Plus this int
pub size_in_bytes: u32, pub size_in_bytes: u32,
#[br(temp)] #[br(pad_before = 4, parse_with = read_string_with_length)]
#[bw(ignore)] #[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<u8> | String::from_utf8(x).unwrap().trim_matches(char::from(0)).to_string())]
pub key_name: String, pub key_name: String,
#[br(temp)] #[br(parse_with = read_string_with_length)]
#[bw(ignore)] #[bw(ignore)]
pub value_name_length: u32,
#[br(count = value_name_length)]
#[bw(map = |x : &String | x.as_bytes())]
#[br(map = | x: Vec<u8> | String::from_utf8(x).unwrap().trim_matches(char::from(0)).to_string())]
pub value_name: String, pub value_name: String,
#[br(pad_before = 5)] #[br(pad_before = 5)]
pub key_type: KeyType, pub key_type: KeyType,
#[br(parse_with = custom_parser, args(size_in_bytes, &key_type, &value_name))] #[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 { let String(value_property) = &decoded.entries.first().unwrap().value else {
panic!("StrProperty!") panic!("StrProperty!")
}; };
assert_eq!(key_property.r#enum, "AR0XJGFWA6HNIQ1AAUJ9UR828"); assert_eq!(key_property.value, "AR0XJGFWA6HNIQ1AAUJ9UR828");
assert_eq!(value_property.name, "NAME 1"); assert_eq!(value_property.value, "NAME 1");
} }
#[test] #[test]
@ -315,7 +283,7 @@ mod tests {
let Int(value_property) = &decoded.entries.first().unwrap().value else { let Int(value_property) = &decoded.entries.first().unwrap().value else {
panic!("Int!") panic!("Int!")
}; };
assert_eq!(key_property.r#enum, "SelectedMachine"); assert_eq!(key_property.value, "SelectedMachine");
assert_eq!(value_property.value, 2); assert_eq!(value_property.value, 2);
// TODO: test the rest of the values // TODO: test the rest of the values
} }

View file

@ -1,27 +1,19 @@
use crate::StringBasedProperty; use crate::StringBasedProperty;
use crate::common::read_string_with_length;
use binrw::binrw; use binrw::binrw;
#[binrw] #[binrw]
#[derive(Debug)] #[derive(Debug)]
pub struct SetEntry { pub struct SetEntry {
#[br(temp)] #[br(parse_with = read_string_with_length)]
#[bw(ignore)] #[bw(ignore)]
pub unk_name_length: u32,
#[br(count = unk_name_length)]
#[bw(map = |x : &String | x.as_bytes())]
#[br(map = | x: Vec<u8> | String::from_utf8(x).unwrap().trim_matches(char::from(0)).to_string())]
pub unk_name: String, pub unk_name: String,
#[br(temp)] #[br(parse_with = read_string_with_length)]
#[bw(ignore)] #[bw(ignore)]
pub unk_type_length: u32,
#[br(count = unk_type_length)]
#[bw(map = |x : &String | x.as_bytes())]
#[br(map = | x: Vec<u8> | String::from_utf8(x).unwrap().trim_matches(char::from(0)).to_string())]
pub unk_type: String, pub unk_type: String,
#[br(args { magic: &unk_type })] #[br(args { magic: &unk_type })]
pub key: StringBasedProperty, pub key: StringBasedProperty,
} }
@ -29,13 +21,9 @@ pub struct SetEntry {
#[derive(Debug)] #[derive(Debug)]
pub struct SetProperty { pub struct SetProperty {
pub unk: u32, pub unk: u32,
#[br(temp)]
#[br(pad_before = 4, parse_with = read_string_with_length)]
#[bw(ignore)] #[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<u8> | String::from_utf8(x).unwrap().trim_matches(char::from(0)).to_string())]
pub key_name: String, pub key_name: String,
#[br(pad_before = 5)] #[br(pad_before = 5)]

View file

@ -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; use binrw::binrw;
#[binrw] #[binrw]
@ -17,11 +22,9 @@ pub enum Struct {
#[br(magic = b"PrimaryAssetType\0")] #[br(magic = b"PrimaryAssetType\0")]
PrimaryAssetType { PrimaryAssetType {
#[br(pad_before = 17)] #[br(pad_before = 17)]
name: PrimaryAssetNameProperty, name: PrimaryAssetNameProperty,
#[br(pad_before = 9)] // "None" and it's length in bytes plus the null terminator #[br(pad_before = 9)] // "None" and it's length in bytes plus the null terminator
#[br(pad_after = 9)] // ditto #[br(pad_after = 9)] // ditto
primary_asset_name: PrimaryAssetNameProperty, primary_asset_name: PrimaryAssetNameProperty,
}, },
#[br(magic = b"PrimaryAssetId\0")] #[br(magic = b"PrimaryAssetId\0")]
@ -42,7 +45,6 @@ pub enum Struct {
LinearColor(LinearColorStruct), LinearColor(LinearColorStruct),
#[br(magic = b"CarryCount\0")] #[br(magic = b"CarryCount\0")]
CarryCount { CarryCount {
carry_count: CarryCountProperty, carry_count: CarryCountProperty,
#[br(pad_before = 15)] // "StoreCount" + 4 bytes for length + 1 byte for endofstring #[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")] #[br(magic = b"Map\0")]
Map { Map {
#[br(pad_after = 9)] // "None" + 1 byte for endofstring + 4 bytes for length #[br(pad_after = 9)] // "None" + 1 byte for endofstring + 4 bytes for length
map: CarryCountProperty, map: CarryCountProperty,
}, },
@ -63,23 +64,18 @@ pub enum Struct {
#[br(pad_after = 9)] // "None" and it's length in bytes plus the null terminator #[br(pad_after = 9)] // "None" and it's length in bytes plus the null terminator
name: PrimaryAssetNameProperty, name: PrimaryAssetNameProperty,
#[br(pad_after = 9)] // "None" and it's length in bytes plus the null terminator #[br(pad_after = 9)] // "None" and it's length in bytes plus the null terminator
primary_asset_name: PrimaryAssetNameProperty, primary_asset_name: PrimaryAssetNameProperty,
data: [u8; 137], data: [u8; 137],
}, },
#[br(magic = b"Set\0")] #[br(magic = b"Set\0")]
Set { Set {
#[br(pad_after = 9)] // "None" + 1 byte for endofstring + 4 bytes for length #[br(pad_after = 9)] // "None" + 1 byte for endofstring + 4 bytes for length
set: CarryCountProperty, set: CarryCountProperty,
}, },
#[br(magic = b"ItemSlots\0")] #[br(magic = b"ItemSlots\0")]
ItemSlots { ItemSlots { unk: [u8; 2125] },
unk: [u8; 2125]
},
} }
#[binrw] #[binrw]

View file

@ -38,24 +38,20 @@ pub struct ParamsStruct {
pub struct PrimaryAssetNameProperty { pub struct PrimaryAssetNameProperty {
#[br(temp)] #[br(temp)]
#[bw(ignore)] #[bw(ignore)]
pub property_name_length: u32, pub property_name_length: u32,
#[br(count = property_name_length)] #[br(count = property_name_length)]
#[bw(map = |x : &String | x.as_bytes())] #[bw(map = |x : &String | x.as_bytes())]
#[br(map = | x: Vec<u8> | String::from_utf8(x).unwrap().trim_matches(char::from(0)).to_string())] #[br(map = | x: Vec<u8> | String::from_utf8(x).unwrap().trim_matches(char::from(0)).to_string())]
pub property_name: String, pub property_name: String,
#[br(temp)] #[br(temp)]
#[bw(ignore)] #[bw(ignore)]
#[br(if(property_name != "None"))] #[br(if(property_name != "None"))]
pub type_length: u32, pub type_length: u32,
#[br(count = type_length)] #[br(count = type_length)]
#[bw(map = |x : &String | x.as_bytes())] #[bw(map = |x : &String | x.as_bytes())]
#[br(map = | x: Vec<u8> | String::from_utf8(x).unwrap().trim_matches(char::from(0)).to_string())] #[br(map = | x: Vec<u8> | String::from_utf8(x).unwrap().trim_matches(char::from(0)).to_string())]
#[br(if(property_name != "None"))] #[br(if(property_name != "None"))]
pub type_name: String, pub type_name: String,
#[br(if(property_name != "None"))] #[br(if(property_name != "None"))]
@ -64,18 +60,15 @@ pub struct PrimaryAssetNameProperty {
pub key: Option<Box<StringBasedProperty>>, pub key: Option<Box<StringBasedProperty>>,
} }
#[binrw] #[binrw]
#[derive(Debug)] #[derive(Debug)]
pub struct CarryCountProperty { pub struct CarryCountProperty {
#[br(temp)] #[br(temp)]
#[bw(ignore)] #[bw(ignore)]
pub property_name_length: u32, pub property_name_length: u32,
#[br(count = property_name_length)] #[br(count = property_name_length)]
#[bw(map = |x : &String | x.as_bytes())] #[bw(map = |x : &String | x.as_bytes())]
#[br(map = | x: Vec<u8> | String::from_utf8(x).unwrap().trim_matches(char::from(0)).to_string())] #[br(map = | x: Vec<u8> | String::from_utf8(x).unwrap().trim_matches(char::from(0)).to_string())]
pub property_name: String, pub property_name: String,
#[br(args { magic: &property_name})] #[br(args { magic: &property_name})]
@ -94,7 +87,7 @@ pub struct PrimaryAssetIdStruct {
#[derive(Debug)] #[derive(Debug)]
pub struct DAModuleItemDataStruct { pub struct DAModuleItemDataStruct {
#[br(pad_before = 17)] #[br(pad_before = 17)]
pub module_level: PrimaryAssetNameProperty pub module_level: PrimaryAssetNameProperty,
} }
#[binrw] #[binrw]