2025-02-23 16:50:10 -05:00
|
|
|
use crate::common::{read_bool_from, read_string_with_length, write_string_with_length};
|
2025-02-19 19:34:13 -05:00
|
|
|
use crate::struct_property::Struct;
|
|
|
|
use crate::structs::PrimaryAssetNameProperty;
|
|
|
|
use binrw::helpers::until_exclusive;
|
2025-02-23 15:25:50 -05:00
|
|
|
use binrw::{BinRead, BinResult, binrw};
|
2025-02-23 16:14:44 -05:00
|
|
|
use crate::guid::Guid;
|
2025-02-19 19:34:13 -05:00
|
|
|
|
|
|
|
// A struct without a name
|
|
|
|
#[binrw]
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct AnonymousStruct {
|
|
|
|
#[br(parse_with = until_exclusive(|entry: &PrimaryAssetNameProperty| entry.property_name == "None"))]
|
|
|
|
fields: Vec<PrimaryAssetNameProperty>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[binrw]
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct MapSubStructProperty {
|
2025-02-23 14:38:35 -05:00
|
|
|
#[br(temp)]
|
|
|
|
#[bw(ignore)]
|
|
|
|
pub unk_name_length: u32,
|
|
|
|
pub r#struct: Struct,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[binrw]
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct StructMaybeKey {
|
2025-02-23 15:25:50 -05:00
|
|
|
#[br(parse_with = read_string_with_length)]
|
2025-02-23 16:50:10 -05:00
|
|
|
#[bw(write_with = write_string_with_length)]
|
2025-02-19 19:34:13 -05:00
|
|
|
pub unk_name: String,
|
|
|
|
|
2025-02-23 16:50:10 -05:00
|
|
|
#[brw(pad_after = 8)]
|
|
|
|
#[br(parse_with = read_string_with_length)]
|
|
|
|
#[bw(write_with = write_string_with_length)]
|
2025-02-19 19:34:13 -05:00
|
|
|
pub unk_type: String,
|
2025-02-23 14:38:35 -05:00
|
|
|
|
|
|
|
#[br(pad_before = 4)] // type length
|
2025-02-19 19:34:13 -05:00
|
|
|
pub r#struct: Struct,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[binrw]
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct MapSubFloatProperty {
|
|
|
|
pub value: f32,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[binrw]
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct MapSubNameProperty {
|
2025-02-23 15:25:50 -05:00
|
|
|
#[br(parse_with = read_string_with_length)]
|
2025-02-23 16:50:10 -05:00
|
|
|
#[bw(write_with = write_string_with_length)]
|
2025-02-23 15:25:50 -05:00
|
|
|
pub value: String,
|
2025-02-19 19:34:13 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
#[binrw]
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct MapSubStrProperty {
|
2025-02-23 15:25:50 -05:00
|
|
|
#[br(parse_with = read_string_with_length)]
|
2025-02-23 16:50:10 -05:00
|
|
|
#[bw(write_with = write_string_with_length)]
|
2025-02-23 15:25:50 -05:00
|
|
|
pub value: String,
|
2025-02-19 19:34:13 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
#[binrw]
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct MapSubBoolProperty {
|
|
|
|
#[br(map = read_bool_from::<u8>)]
|
|
|
|
#[bw(ignore)]
|
|
|
|
pub value: bool,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[binrw]
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct MapSubIntProperty {
|
|
|
|
pub value: u32,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[binrw]
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct MapSubEnumProperty {
|
2025-02-23 15:25:50 -05:00
|
|
|
#[br(parse_with = read_string_with_length)]
|
2025-02-23 16:50:10 -05:00
|
|
|
#[bw(write_with = write_string_with_length)]
|
2025-02-23 15:25:50 -05:00
|
|
|
pub value: String,
|
2025-02-19 19:34:13 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// Used in MapProperty exclusively, seems to be a shortened version of some Properties
|
|
|
|
#[binrw]
|
|
|
|
#[derive(Debug)]
|
2025-02-23 14:43:10 -05:00
|
|
|
#[br(import { magic: &str })]
|
2025-02-19 19:34:13 -05:00
|
|
|
pub enum MabSubProperty {
|
|
|
|
#[br(pre_assert("NameProperty" == magic))]
|
|
|
|
Name(MapSubNameProperty),
|
2025-02-23 14:38:35 -05:00
|
|
|
#[br(pre_assert("StructProperty" == magic))]
|
2025-02-19 19:34:13 -05:00
|
|
|
Struct(MapSubStructProperty),
|
|
|
|
#[br(pre_assert("FloatProperty" == magic))]
|
|
|
|
Float(MapSubFloatProperty),
|
|
|
|
#[br(pre_assert("StrProperty" == magic))]
|
|
|
|
String(MapSubStrProperty),
|
|
|
|
#[br(pre_assert("BoolProperty" == magic))]
|
|
|
|
Bool(MapSubBoolProperty),
|
|
|
|
#[br(pre_assert("IntProperty" == magic))]
|
|
|
|
Int(MapSubIntProperty),
|
|
|
|
#[br(pre_assert("EnumProperty" == magic))]
|
|
|
|
Enum(MapSubEnumProperty),
|
|
|
|
}
|
|
|
|
|
2025-02-23 14:38:35 -05:00
|
|
|
#[binrw]
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct StringMapKey {
|
2025-02-23 15:25:50 -05:00
|
|
|
#[br(parse_with = read_string_with_length)]
|
2025-02-23 16:50:10 -05:00
|
|
|
#[bw(write_with = write_string_with_length)]
|
2025-02-23 15:25:50 -05:00
|
|
|
pub value: String,
|
2025-02-23 14:38:35 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
#[binrw]
|
|
|
|
#[derive(Debug)]
|
|
|
|
#[br(import { magic: &KeyType })]
|
|
|
|
pub enum MapKeyProperty {
|
|
|
|
#[br(pre_assert(*magic == KeyType::String))]
|
|
|
|
String(StringMapKey),
|
|
|
|
#[br(pre_assert(*magic == KeyType::StructMaybe))]
|
|
|
|
StructMaybe(StructMaybeKey),
|
|
|
|
#[br(pre_assert(*magic == KeyType::Enum))]
|
|
|
|
Enum(MapSubEnumProperty),
|
|
|
|
#[br(pre_assert(*magic == KeyType::EnumAgain))]
|
|
|
|
EnumAgain(MapSubEnumProperty),
|
|
|
|
#[br(pre_assert(*magic == KeyType::GUID))]
|
2025-02-23 16:14:44 -05:00
|
|
|
GUID(Guid),
|
2025-02-23 14:38:35 -05:00
|
|
|
#[br(pre_assert(*magic == KeyType::SomeID))]
|
2025-02-23 16:14:44 -05:00
|
|
|
SomeID(Guid),
|
2025-02-23 14:38:35 -05:00
|
|
|
#[br(pre_assert(*magic == KeyType::SomeID2))]
|
2025-02-23 16:14:44 -05:00
|
|
|
SomeID2(Guid),
|
2025-02-23 14:38:35 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
#[binrw]
|
|
|
|
#[derive(Debug)]
|
|
|
|
#[br(import(key_type: &KeyType, value_type: &str))]
|
2025-02-19 19:34:13 -05:00
|
|
|
pub struct MapEntry {
|
2025-02-23 14:38:35 -05:00
|
|
|
#[br(args { magic: key_type})]
|
|
|
|
pub key: MapKeyProperty,
|
2025-02-19 19:34:13 -05:00
|
|
|
|
2025-02-23 14:43:10 -05:00
|
|
|
#[br(args { magic: value_type })]
|
2025-02-19 19:34:13 -05:00
|
|
|
pub value: MabSubProperty,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[binrw]
|
2025-02-23 14:38:35 -05:00
|
|
|
#[brw(repr = u32)]
|
2025-02-23 15:25:50 -05:00
|
|
|
#[derive(Debug, PartialEq, Clone)]
|
2025-02-23 14:38:35 -05:00
|
|
|
pub enum KeyType {
|
|
|
|
String = 0x1,
|
|
|
|
GUID = 0x3,
|
|
|
|
SomeID = 0x6,
|
|
|
|
SomeID2 = 0x10,
|
|
|
|
EnumAgain = 0x4,
|
|
|
|
Enum = 0x7,
|
|
|
|
StructMaybe = 0xC,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[binrw::parser(reader, endian)]
|
2025-02-23 15:25:50 -05:00
|
|
|
fn custom_parser(
|
|
|
|
size_in_bytes: u32,
|
|
|
|
key_type: &KeyType,
|
|
|
|
value_type: &str,
|
|
|
|
) -> BinResult<Vec<MapEntry>> {
|
2025-02-23 14:38:35 -05:00
|
|
|
let mut result = Vec::<MapEntry>::new();
|
|
|
|
|
2025-02-23 15:26:52 -05:00
|
|
|
let mut current = reader.stream_position()?;
|
2025-02-23 14:38:35 -05:00
|
|
|
let end = current + size_in_bytes as u64 - 5 - 3;
|
2025-02-23 14:43:10 -05:00
|
|
|
|
2025-02-23 14:38:35 -05:00
|
|
|
while current < end {
|
2025-02-23 16:03:08 -05:00
|
|
|
result.push(MapEntry::read_options(
|
|
|
|
reader,
|
|
|
|
endian,
|
|
|
|
(key_type, value_type),
|
|
|
|
)?);
|
2025-02-23 15:26:52 -05:00
|
|
|
current = reader.stream_position()?;
|
2025-02-23 14:38:35 -05:00
|
|
|
}
|
|
|
|
Ok(result)
|
2025-02-19 19:34:13 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
#[binrw]
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct MapProperty {
|
|
|
|
// Plus this int
|
|
|
|
pub size_in_bytes: u32,
|
|
|
|
|
2025-02-23 16:50:10 -05:00
|
|
|
#[brw(pad_before = 4)]
|
|
|
|
#[br(parse_with = read_string_with_length)]
|
|
|
|
#[bw(write_with = write_string_with_length)]
|
2025-02-19 19:34:13 -05:00
|
|
|
pub key_name: String,
|
|
|
|
|
2025-02-23 15:25:50 -05:00
|
|
|
#[br(parse_with = read_string_with_length)]
|
2025-02-23 16:50:10 -05:00
|
|
|
#[bw(write_with = write_string_with_length)]
|
2025-02-19 19:34:13 -05:00
|
|
|
pub value_name: String,
|
|
|
|
|
2025-02-23 16:50:10 -05:00
|
|
|
#[brw(pad_before = 5)]
|
2025-02-23 14:38:35 -05:00
|
|
|
pub key_type: KeyType,
|
|
|
|
|
|
|
|
#[br(parse_with = custom_parser, args(size_in_bytes, &key_type, &value_name))]
|
|
|
|
pub entries: Vec<MapEntry>,
|
2025-02-19 19:34:13 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
2025-02-23 14:43:10 -05:00
|
|
|
use crate::map_property::MabSubProperty::{Int, String};
|
2025-02-19 19:34:13 -05:00
|
|
|
use binrw::BinRead;
|
|
|
|
use std::io::Cursor;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn simple_strmap() {
|
|
|
|
// From LocalProfile.sav i think
|
|
|
|
let data = [
|
|
|
|
0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x53, 0x74,
|
|
|
|
0x72, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x00, 0x0c, 0x00, 0x00, 0x00,
|
|
|
|
0x53, 0x74, 0x72, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x00, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x41, 0x52, 0x30,
|
|
|
|
0x58, 0x4a, 0x47, 0x46, 0x57, 0x41, 0x36, 0x48, 0x4e, 0x49, 0x51, 0x31, 0x41, 0x41,
|
|
|
|
0x55, 0x4a, 0x39, 0x55, 0x52, 0x38, 0x32, 0x38, 0x00, 0x07, 0x00, 0x00, 0x00, 0x4e,
|
|
|
|
0x41, 0x4d, 0x45, 0x20, 0x31, 0x00,
|
|
|
|
];
|
|
|
|
let mut cursor = Cursor::new(data);
|
|
|
|
let decoded = MapProperty::read_le(&mut cursor).unwrap();
|
|
|
|
assert_eq!(decoded.size_in_bytes, 49);
|
|
|
|
assert_eq!(decoded.key_name, "StrProperty");
|
|
|
|
assert_eq!(decoded.value_name, "StrProperty");
|
2025-02-23 14:38:35 -05:00
|
|
|
let MapKeyProperty::String(key_property) = &decoded.entries.first().unwrap().key else {
|
2025-02-19 19:34:13 -05:00
|
|
|
panic!("StrProperty!")
|
|
|
|
};
|
|
|
|
let String(value_property) = &decoded.entries.first().unwrap().value else {
|
|
|
|
panic!("StrProperty!")
|
|
|
|
};
|
2025-02-23 15:25:50 -05:00
|
|
|
assert_eq!(key_property.value, "AR0XJGFWA6HNIQ1AAUJ9UR828");
|
|
|
|
assert_eq!(value_property.value, "NAME 1");
|
2025-02-19 19:34:13 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn name_int_map() {
|
|
|
|
// From Persistent.sav, EventParams
|
|
|
|
let data = [
|
|
|
|
0xba, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x4e, 0x61,
|
|
|
|
0x6d, 0x65, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x00, 0x0c, 0x00, 0x00,
|
|
|
|
0x00, 0x49, 0x6e, 0x74, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x00, 0x00,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x53, 0x65,
|
|
|
|
0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x00,
|
|
|
|
0x02, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x54, 0x75, 0x74, 0x6f, 0x72, 0x69,
|
|
|
|
0x61, 0x6c, 0x5f, 0x50, 0x6f, 0x72, 0x74, 0x61, 0x6c, 0x5f, 0x30, 0x32, 0x00, 0x01,
|
|
|
|
0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x59, 0x4f,
|
|
|
|
0x5f, 0x66, 0x6c, 0x30, 0x30, 0x33, 0x00, 0x01, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00,
|
|
|
|
0x00, 0x4d, 0x30, 0x31, 0x41, 0x30, 0x31, 0x5f, 0x4e, 0x65, 0x78, 0x74, 0x53, 0x74,
|
|
|
|
0x61, 0x67, 0x65, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x4d, 0x30,
|
|
|
|
0x31, 0x41, 0x30, 0x31, 0x5f, 0x47, 0x61, 0x74, 0x65, 0x30, 0x30, 0x31, 0x00, 0x01,
|
|
|
|
0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x4d, 0x30, 0x31, 0x41, 0x30, 0x31, 0x4d,
|
|
|
|
0x6f, 0x6e, 0x6f, 0x72, 0x61, 0x69, 0x6c, 0x30, 0x30, 0x31, 0x54, 0x65, 0x73, 0x74,
|
|
|
|
0x30, 0x30, 0x30, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x4d,
|
|
|
|
0x30, 0x31, 0x41, 0x30, 0x31, 0x5f, 0x47, 0x61, 0x74, 0x65, 0x30, 0x30, 0x35, 0x00,
|
|
|
|
0x01, 0x00, 0x00, 0x00,
|
|
|
|
];
|
|
|
|
let mut cursor = Cursor::new(data);
|
|
|
|
let decoded = MapProperty::read_le(&mut cursor).unwrap();
|
|
|
|
assert_eq!(decoded.size_in_bytes, 186);
|
|
|
|
assert_eq!(decoded.key_name, "NameProperty");
|
|
|
|
assert_eq!(decoded.value_name, "IntProperty");
|
2025-02-23 14:38:35 -05:00
|
|
|
let MapKeyProperty::Enum(key_property) = &decoded.entries.first().unwrap().key else {
|
2025-02-19 19:34:13 -05:00
|
|
|
panic!("Name!")
|
|
|
|
};
|
|
|
|
let Int(value_property) = &decoded.entries.first().unwrap().value else {
|
|
|
|
panic!("Int!")
|
|
|
|
};
|
2025-02-23 15:25:50 -05:00
|
|
|
assert_eq!(key_property.value, "SelectedMachine");
|
2025-02-23 14:38:35 -05:00
|
|
|
assert_eq!(value_property.value, 2);
|
2025-02-19 19:34:13 -05:00
|
|
|
// TODO: test the rest of the values
|
|
|
|
}
|
|
|
|
}
|