1use serde_derive::{Deserialize, Serialize};
52
53use std::cmp::Reverse;
54use std::collections::{BTreeMap, HashMap};
55use std::fmt::Debug;
56use std::fs::File;
57use std::io::{BufReader, BufWriter, Write};
58use std::mem::{discriminant, Discriminant};
59use std::ops::RangeInclusive;
60use std::path::Path;
61
62use rpfm_lib::error::{RLibError, Result};
63use rpfm_lib::files::esf::{ESF, NodeType, RecordNode, RecordNodeFlags};
64
65#[cfg(test)] #[path = "layout_test.rs"] mod layout_test;
66
67pub const STARTPOS_LAYOUT_FOLDER: &str = "startpos_layout";
69
70const START_ANCHOR: &str = "^";
72
73pub const MAX_LAYOUT_SLOTS: usize = 256;
75
76const MAX_ALIGNMENT_CELLS: usize = 1 << 20;
78
79const MAX_SLOT_DEFAULT_LEAVES: usize = 16;
81
82const MAX_DEFAULT_ARRAY_LEN: usize = 64;
84
85pub const VARIANT_ARRAYS: [&str; 3] = ["CAI_BDI_POOL_BELIEFS", "CAI_BDI_POOL_DESIRES", "CAI_BDI_POOL_INTENTIONS"];
87
88pub(crate) const MAX_COMPACT_U32: u32 = 0xFF_FFFF;
90
91pub(crate) const COMPACT_I32: RangeInclusive<i32> = -0x80_0000..=0x7F_FFFF;
93
94#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
100pub struct StartposLayout {
101
102 records: BTreeMap<String, RecordLayout>,
104}
105
106#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
108pub struct RecordLayout {
109
110 name: String,
112
113 version: u8,
115
116 flags: u8,
118
119 array: bool,
121
122 #[serde(default)]
124 free_form: bool,
125
126 slots: Vec<Slot>,
128}
129
130#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
132pub enum Slot {
133
134 Leaf {
136
137 name: String,
139
140 default: NodeType,
142
143 #[serde(default)]
145 optional: bool,
146
147 #[serde(default)]
149 absent_by_default: bool,
150 },
151
152 Record {
154
155 key: String,
157
158 optional: bool,
160
161 #[serde(default, skip_serializing_if = "Option::is_none")]
164 default: Option<RecordNode>,
165 },
166}
167
168#[derive(Debug, Clone)]
170pub struct RecordBuilder<'a> {
171 layout: &'a StartposLayout,
172 path: String,
173 record: &'a RecordLayout,
174 groups: Vec<GroupBuilder<'a>>,
175}
176
177#[derive(Debug, Clone)]
179pub struct GroupBuilder<'a> {
180 path: String,
181 record: &'a RecordLayout,
182 values: Vec<Option<NodeType>>,
183}
184
185#[derive(Debug, Clone, Default)]
188pub struct GroupView<'a> {
189 fields: HashMap<&'a str, &'a NodeType>,
190}
191
192#[derive(Debug, Clone)]
194enum SlotStats {
195 Leaf { kind: Discriminant<NodeType>, values: HashMap<String, (usize, NodeType)> },
196 Record { key: String, seen: usize, contents: Option<HashMap<String, (usize, RecordNode)>> },
197}
198
199#[derive(Debug, Clone, Default)]
201struct PathStats {
202 name: String,
203 versions: HashMap<u8, usize>,
204 flags: HashMap<u8, usize>,
205 array: bool,
206 free_form: bool,
207 groups: usize,
208 slots: Vec<SlotStats>,
209}
210
211impl StartposLayout {
216
217 pub fn from_esf(startpos: &ESF) -> Result<Self> {
227 let mut stats = HashMap::new();
228 collect_startpos(startpos, &mut stats)?;
229 Self::from_stats(stats)
230 }
231
232 pub fn from_esfs(startposes: impl IntoIterator<Item = Result<ESF>>) -> Result<Self> {
244 let mut stats = HashMap::new();
245 for startpos in startposes {
246 collect_startpos(&startpos?, &mut stats)?;
247 }
248 Self::from_stats(stats)
249 }
250
251 pub fn with_fallback(mut self, fallback: &StartposLayout) -> Self {
260 for (path, record) in &fallback.records {
261 match self.records.get_mut(path) {
262 Some(own) if own.slots.is_empty() => *own = record.clone(),
263 Some(own) => add_missing_slots(&mut own.slots, &record.slots),
264 None => {
265 self.records.insert(path.to_owned(), record.clone());
266 },
267 }
268 }
269 self
270 }
271
272 fn from_stats(stats: HashMap<String, PathStats>) -> Result<Self> {
274 let records = stats.into_iter().map(|(path, stats)| (path, stats.into_layout())).collect();
275 let mut layout = Self { records };
276 layout.drop_redundant_slot_defaults()?;
277 Ok(layout)
278 }
279
280 fn drop_redundant_slot_defaults(&mut self) -> Result<()> {
282 let mut redundant = vec![];
283 for (path, record) in &self.records {
284 for (index, slot) in record.slots.iter().enumerate() {
285 if let Slot::Record { key, default: Some(default), .. } = slot {
286 if *default == self.default_record(&child_path(path, key))? {
287 redundant.push((path.to_owned(), index));
288 }
289 }
290 }
291 }
292
293 for (path, index) in redundant {
294 if let Some(Slot::Record { default, .. }) = self.records.get_mut(&path).and_then(|record| record.slots.get_mut(index)) {
295 *default = None;
296 }
297 }
298 Ok(())
299 }
300
301 pub fn load(path: &Path) -> Result<Self> {
307 let file = BufReader::new(File::open(path)?);
308 ron::de::from_reader(file).map_err(|error| RLibError::StartposLayoutError(format!("invalid startpos layout: {error}")))
309 }
310
311 pub fn save(&self, path: &Path) -> Result<()> {
317 let mut file = BufWriter::new(File::create(path)?);
318 let config = ron::ser::PrettyConfig::default();
319 let text = ron::ser::to_string_pretty(self, config).map_err(|error| RLibError::StartposLayoutError(format!("can't serialize the startpos layout: {error}")))?;
320 file.write_all(text.as_bytes())?;
321 Ok(())
322 }
323
324 pub fn record(&self, path: &str) -> Option<&RecordLayout> {
326 self.records.get(path)
327 }
328
329 pub fn builder(&self, path: &str) -> Result<RecordBuilder<'_>> {
343 let record = self.records.get(path).ok_or_else(|| missing(path))?;
344 let mut builder = RecordBuilder { layout: self, path: path.to_owned(), record, groups: vec![] };
345 if !record.array {
346 builder.push_group()?;
347 }
348 Ok(builder)
349 }
350
351 pub fn default_record(&self, path: &str) -> Result<RecordNode> {
357 self.builder(path)?.build()
358 }
359
360 pub fn read<'a>(&'a self, path: &str, record: &'a RecordNode) -> Result<Vec<GroupView<'a>>> {
371 let layout = self.record(path).ok_or_else(|| missing(path))?;
372 record.children().iter().enumerate().map(|(index, group)| {
373 let slots = layout.match_children(group).ok_or_else(|| missing(&format!("{path}[{index}]")))?;
374 Ok(GroupView { fields: slots.into_iter().map(Slot::id).zip(group).collect() })
375 }).collect()
376 }
377
378 pub fn read_single<'a>(&'a self, path: &str, record: &'a RecordNode) -> Result<GroupView<'a>> {
389 self.read(path, record)?.into_iter().next().ok_or_else(|| missing(&format!("group in {path}")))
390 }
391
392 pub fn read_items<'a>(&'a self, array_path: &str, parent: &GroupView<'a>, array_key: &str, item_key: &str) -> Result<Vec<(GroupView<'a>, GroupView<'a>)>> {
409 let Some(array) = parent.record(array_key) else { return Ok(vec![]) };
410 let item_path = child_path(array_path, item_key);
411 self.read(array_path, array)?.into_iter()
412 .map(|entry| {
413 let item = entry.record(item_key).ok_or_else(|| missing(&item_path))?;
414 Ok((entry.clone(), self.read_single(&item_path, item)?))
415 })
416 .collect()
417 }
418
419 pub fn raw_record(&self, path: &str, children: Vec<NodeType>) -> Result<RecordNode> {
426 let record = self.records.get(path).ok_or_else(|| missing(path))?;
427 let mut builder = RecordBuilder { layout: self, path: path.to_owned(), record, groups: vec![] };
428 builder.push_children(children);
429 builder.build()
430 }
431}
432
433impl RecordLayout {
434
435 pub fn name(&self) -> &str {
437 &self.name
438 }
439
440 pub fn version(&self) -> u8 {
442 self.version
443 }
444
445 pub fn array(&self) -> bool {
447 self.array
448 }
449
450 pub fn free_form(&self) -> bool {
452 self.free_form
453 }
454
455 pub fn slots(&self) -> &[Slot] {
457 &self.slots
458 }
459
460 pub fn match_children(&self, group: &[NodeType]) -> Option<Vec<&Slot>> {
470 if self.free_form {
471 return None;
472 }
473 let tokens = group_tokens(group);
474 let left = self.slots.iter().map(Slot::kind).collect::<Vec<_>>();
475 let right = tokens.iter().map(token_kind).collect::<Vec<_>>();
476 let pairs = align_kinds(&left, &right);
477 (pairs.len() == tokens.len()).then(|| pairs.into_iter().map(|(slot, _)| &self.slots[slot]).collect())
478 }
479
480 fn leaf_position(&self, path: &str, name: &str) -> Result<usize> {
482 self.slots.iter()
483 .position(|slot| matches!(slot, Slot::Leaf { name: slot_name, .. } if slot_name == name))
484 .ok_or_else(|| missing(&format!("{path}:{name}")))
485 }
486
487 fn record_position(&self, path: &str, key: &str) -> Result<usize> {
489 self.slots.iter()
490 .position(|slot| matches!(slot, Slot::Record { key: slot_key, .. } if slot_key == key))
491 .ok_or_else(|| missing(&format!("{path}/{key}")))
492 }
493}
494
495impl<'a> RecordBuilder<'a> {
496
497 pub fn push_group(&mut self) -> Result<&mut GroupBuilder<'a>> {
507 let path = self.path.to_owned();
508 self.push_group_of(path, self.record)
509 }
510
511 pub fn push_variant(&mut self, code: u32) -> Result<&mut GroupBuilder<'a>> {
525 let path = variant_path(&self.path, code);
526 let layout = self.layout;
527 let record = layout.records.get(&path).ok_or_else(|| missing(&path))?;
528 self.push_group_of(path, record)
529 }
530
531 fn push_group_of(&mut self, path: String, record: &'a RecordLayout) -> Result<&mut GroupBuilder<'a>> {
533 let mut values = vec![];
534 for slot in &record.slots {
535 values.push(match slot {
536 Slot::Leaf { absent_by_default: true, .. } => None,
537 Slot::Leaf { default, .. } => Some(default.clone()),
538 Slot::Record { optional: true, .. } => None,
539 Slot::Record { default: Some(default), .. } => Some(NodeType::Record(Box::new(default.clone()))),
540 Slot::Record { key, default: None, .. } => Some(NodeType::Record(Box::new(self.layout.default_record(&child_path(&path, key))?))),
541 });
542 }
543 self.groups.push(GroupBuilder { path, record, values });
544 Ok(self.groups.last_mut().expect("a group was just pushed"))
545 }
546
547 pub fn push_children(&mut self, children: Vec<NodeType>) {
550 let values = children.into_iter().map(Some).collect();
551 self.groups.push(GroupBuilder { path: self.path.to_owned(), record: self.record, values });
552 }
553
554 pub fn group(&mut self) -> Result<&mut GroupBuilder<'a>> {
560 let path = self.path.to_owned();
561 self.groups.first_mut().ok_or_else(|| missing(&format!("{path}[0]")))
562 }
563
564 pub fn build(self) -> Result<RecordNode> {
570 let mut record = RecordNode::default();
571 record.set_name(self.record.name.to_owned());
572 record.set_version(self.record.version);
573 record.set_record_flags(RecordNodeFlags::from_bits_truncate(self.record.flags));
574 record.set_children(self.groups.into_iter().map(|group| group.values.into_iter().flatten().collect()).collect());
575 Ok(record)
576 }
577}
578
579impl GroupBuilder<'_> {
580
581 pub fn set(&mut self, name: &str, value: NodeType) -> Result<&mut Self> {
592 let position = self.record.leaf_position(&self.path, name)?;
593 let Some(Slot::Leaf { default, .. }) = self.record.slots.get(position) else { unreachable!("leaf_position returns leaf slots") };
594 if discriminant(default) != discriminant(&value) {
595 return Err(RLibError::StartposLayoutError(format!("{}:{name}: expected a value like {default:?}, got {value:?}", self.path)));
596 }
597 self.values[position] = Some(value);
598 Ok(self)
599 }
600
601 pub fn set_u32(&mut self, name: &str, value: u32) -> Result<&mut Self> {
610 let mut node = self.default_of(name)?;
611 match &mut node {
612 NodeType::U32(node) => { node.set_value(value).set_optimized(value <= MAX_COMPACT_U32); },
613 NodeType::U16(node) => { *node = u16::try_from(value).map_err(|_| self.wrong_type(name, &value))?; },
614 NodeType::U8(node) => { *node = u8::try_from(value).map_err(|_| self.wrong_type(name, &value))?; },
615 _ => return Err(self.wrong_type(name, &value)),
616 }
617 self.set(name, node)
618 }
619
620 pub fn set_i32(&mut self, name: &str, value: i32) -> Result<&mut Self> {
626 let mut node = self.default_of(name)?;
627 match &mut node {
628 NodeType::I32(node) => { node.set_value(value).set_optimized(COMPACT_I32.contains(&value)); },
629 NodeType::I16(node) => { *node = i16::try_from(value).map_err(|_| self.wrong_type(name, &value))?; },
630 NodeType::I8(node) => { *node = i8::try_from(value).map_err(|_| self.wrong_type(name, &value))?; },
631 _ => return Err(self.wrong_type(name, &value)),
632 }
633 self.set(name, node)
634 }
635
636 pub fn set_f32(&mut self, name: &str, value: f32) -> Result<&mut Self> {
642 let mut node = self.default_of(name)?;
643 match &mut node {
644 NodeType::F32(node) => { node.set_value(value).set_optimized(value == 0.0); },
645 _ => return Err(self.wrong_type(name, &value)),
646 }
647 self.set(name, node)
648 }
649
650 pub fn set_bool(&mut self, name: &str, value: bool) -> Result<&mut Self> {
656 let mut node = self.default_of(name)?;
657 match &mut node {
658 NodeType::Bool(node) => { node.set_value(value); },
659 _ => return Err(self.wrong_type(name, &value)),
660 }
661 self.set(name, node)
662 }
663
664 pub fn set_str(&mut self, name: &str, value: &str) -> Result<&mut Self> {
670 let mut node = self.default_of(name)?;
671 match &mut node {
672 NodeType::Ascii(node) | NodeType::Utf16(node) => { *node = value.to_owned(); },
673 _ => return Err(self.wrong_type(name, &value)),
674 }
675 self.set(name, node)
676 }
677
678 pub fn push_child(&mut self, node: NodeType) -> Result<&mut Self> {
684 if !self.record.free_form {
685 return Err(RLibError::StartposLayoutError(format!("{} has a fixed layout", self.path)));
686 }
687 self.values.push(Some(node));
688 Ok(self)
689 }
690
691 pub fn set_strings(&mut self, name: &str, values: Vec<String>) -> Result<&mut Self> {
697 let mut node = self.default_of(name)?;
698 match &mut node {
699 NodeType::AsciiArray(node) | NodeType::Utf16Array(node) => { *node = values; },
700 _ => return Err(self.wrong_type(name, &values)),
701 }
702 self.set(name, node)
703 }
704
705 pub fn set_bytes(&mut self, name: &str, values: Vec<u8>) -> Result<&mut Self> {
711 let mut node = self.default_of(name)?;
712 match &mut node {
713 NodeType::U8Array(node) | NodeType::I8Array(node) => { *node = values; },
714 _ => return Err(self.wrong_type(name, &values)),
715 }
716 self.set(name, node)
717 }
718
719 pub fn set_u32s(&mut self, name: &str, values: Vec<u32>) -> Result<&mut Self> {
726 let mut node = self.default_of(name)?;
727 match &mut node {
728 NodeType::U32Array(node) => {
729 let optimized = !values.is_empty() && values.iter().all(|value| *value <= MAX_COMPACT_U32);
730 node.set_value(values).set_optimized(optimized);
731 },
732 _ => return Err(self.wrong_type(name, &values)),
733 }
734 self.set(name, node)
735 }
736
737 pub fn unset(&mut self, name: &str) -> Result<&mut Self> {
743 let position = self.record.leaf_position(&self.path, name)?;
744 if !matches!(self.record.slots[position], Slot::Leaf { optional: true, .. }) {
745 return Err(RLibError::StartposLayoutError(format!("{}:{name} is always present", self.path)));
746 }
747 self.values[position] = None;
748 Ok(self)
749 }
750
751 pub fn set_record(&mut self, key: &str, record: Option<RecordNode>) -> Result<&mut Self> {
762 let position = self.record.record_position(&self.path, key)?;
763 if record.is_none() && matches!(self.record.slots[position], Slot::Record { optional: false, .. }) {
764 return Err(RLibError::StartposLayoutError(format!("{}/{key} is always present", self.path)));
765 }
766 self.values[position] = record.map(|record| NodeType::Record(Box::new(record)));
767 Ok(self)
768 }
769
770 fn default_of(&self, name: &str) -> Result<NodeType> {
772 match &self.record.slots[self.record.leaf_position(&self.path, name)?] {
773 Slot::Leaf { default, .. } => Ok(default.clone()),
774 Slot::Record { .. } => unreachable!("leaf_position returns leaf slots"),
775 }
776 }
777
778 fn wrong_type(&self, name: &str, value: &dyn Debug) -> RLibError {
780 RLibError::StartposLayoutError(format!("{}:{name}: {value:?} doesn't fit the field", self.path))
781 }
782}
783
784impl<'a> GroupView<'a> {
785
786 pub fn ids(&self) -> impl Iterator<Item = &'a str> + '_ {
788 self.fields.keys().copied()
789 }
790
791 pub fn get(&self, id: &str) -> Option<&'a NodeType> {
793 self.fields.get(id).copied()
794 }
795
796 pub fn u32(&self, name: &str) -> Option<u32> {
798 match self.get(name)? {
799 NodeType::U32(node) => Some(*node.value()),
800 NodeType::U16(value) => Some(u32::from(*value)),
801 NodeType::U8(value) => Some(u32::from(*value)),
802 _ => None,
803 }
804 }
805
806 pub fn i32(&self, name: &str) -> Option<i32> {
808 match self.get(name)? {
809 NodeType::I32(node) => Some(*node.value()),
810 NodeType::I16(value) => Some(i32::from(*value)),
811 NodeType::I8(value) => Some(i32::from(*value)),
812 _ => None,
813 }
814 }
815
816 pub fn bool(&self, name: &str) -> Option<bool> {
818 match self.get(name)? {
819 NodeType::Bool(node) => Some(*node.value()),
820 _ => None,
821 }
822 }
823
824 pub fn str(&self, name: &str) -> Option<&'a str> {
826 match self.get(name)? {
827 NodeType::Ascii(value) | NodeType::Utf16(value) => Some(value),
828 _ => None,
829 }
830 }
831
832 pub fn f32(&self, name: &str) -> Option<f32> {
834 match self.get(name)? {
835 NodeType::F32(node) => Some(*node.value()),
836 _ => None,
837 }
838 }
839
840 pub fn strs(&self, name: &str) -> Option<&'a [String]> {
842 match self.get(name)? {
843 NodeType::AsciiArray(values) | NodeType::Utf16Array(values) => Some(values),
844 _ => None,
845 }
846 }
847
848 pub fn u32s(&self, name: &str) -> Option<&'a [u32]> {
850 match self.get(name)? {
851 NodeType::U32Array(node) => Some(node.value()),
852 _ => None,
853 }
854 }
855
856 pub fn record(&self, key: &str) -> Option<&'a RecordNode> {
858 match self.get(key)? {
859 NodeType::Record(record) => Some(record),
860 _ => None,
861 }
862 }
863}
864
865impl PathStats {
866
867 fn add_group(&mut self, group: &[(Option<String>, &NodeType)]) {
869 self.groups += 1;
870 if self.free_form {
871 return;
872 }
873
874 let same_shape = group.len() == self.slots.len() && group.iter().zip(&self.slots).all(|(token, slot)| token_kind(token) == slot.kind());
876 if same_shape {
877 for (slot, token) in self.slots.iter_mut().zip(group) {
878 slot.record(token.1);
879 }
880 return;
881 }
882
883 let alignment = align(&self.slots, group);
885 let slot_count = self.slots.len();
886 let mut old_slots = std::mem::take(&mut self.slots).into_iter();
887 let mut merged = Vec::with_capacity(slot_count.max(group.len()));
888 let mut slot_index = 0;
889 let mut group_index = 0;
890 let mut pairs = alignment.into_iter().peekable();
891 while slot_index < slot_count || group_index < group.len() {
892 match pairs.peek().copied() {
893 Some((slot, token)) if slot == slot_index && token == group_index => {
894 let mut stats = old_slots.next().expect("slots are consumed in order");
895 stats.record(group[token].1);
896 merged.push(stats);
897 slot_index += 1;
898 group_index += 1;
899 pairs.next();
900 },
901 Some((slot, _)) if slot_index < slot => {
902 merged.push(old_slots.next().expect("slots are consumed in order"));
903 slot_index += 1;
904 },
905 Some(_) => {
906 merged.push(SlotStats::new(&group[group_index]));
907 group_index += 1;
908 },
909 None if slot_index < slot_count => {
910 merged.push(old_slots.next().expect("slots are consumed in order"));
911 slot_index += 1;
912 },
913 None => {
914 merged.push(SlotStats::new(&group[group_index]));
915 group_index += 1;
916 },
917 }
918 }
919 self.slots = merged;
920 if self.slots.len() > MAX_LAYOUT_SLOTS {
921 self.free_form = true;
922 self.slots.clear();
923 }
924 }
925
926 fn into_layout(self) -> RecordLayout {
928 let mut slots = vec![];
929 let mut anchor = START_ANCHOR.to_owned();
930 let mut offset = 0;
931 for slot in self.slots {
932 match slot {
933 SlotStats::Record { key, seen, contents } => {
934 let optional = seen < self.groups;
935 if !optional {
936 anchor = key.to_owned();
937 offset = 0;
938 }
939
940 let default = contents.and_then(|contents| contents.into_iter()
942 .max_by(|(text_a, (count_a, _)), (text_b, (count_b, _))| count_a.cmp(count_b).then_with(|| text_b.cmp(text_a)))
943 .map(|(_, (_, mut record))| {
944 trim_record(&mut record);
945 record
946 }));
947 slots.push(Slot::Record { key, optional, default });
948 },
949 SlotStats::Leaf { values, .. } => {
950 let seen = values.values().map(|(count, _)| count).sum::<usize>();
951 let optional = seen < self.groups;
952 let absent_by_default = seen * 2 < self.groups;
953 offset += 1;
954
955 let (_, (_, mut default)) = values.into_iter().max_by(|(text_a, (count_a, _)), (text_b, (count_b, _))| count_a.cmp(count_b).then_with(|| text_b.cmp(text_a))).expect("leaves have at least one value");
957 trim_default(&mut default);
958 slots.push(Slot::Leaf { name: format!("{anchor}+{offset}"), default, optional, absent_by_default });
959 },
960 }
961 }
962
963 let most_common = |counts: HashMap<u8, usize>| counts.into_iter().max_by_key(|&(value, count)| (count, Reverse(value))).map(|(value, _)| value).unwrap_or_default();
964 RecordLayout {
965 name: self.name,
966 version: most_common(self.versions),
967 flags: most_common(self.flags),
968 array: self.array,
969 free_form: self.free_form,
970 slots,
971 }
972 }
973}
974
975impl SlotStats {
976
977 fn new(token: &(Option<String>, &NodeType)) -> Self {
979 match token {
980 (Some(key), node) => {
981 let mut stats = Self::Record { key: key.to_owned(), seen: 0, contents: Some(HashMap::new()) };
982 stats.record(node);
983 stats
984 },
985 (None, node) => {
986 let mut values = HashMap::new();
987 values.insert(format!("{node:?}"), (1, (*node).clone()));
988 Self::Leaf { kind: discriminant(*node), values }
989 },
990 }
991 }
992
993 fn record(&mut self, node: &NodeType) {
995 match self {
996 Self::Record { seen, contents, .. } => {
997 *seen += 1;
998
999 match (node, contents.as_mut()) {
1001 (NodeType::Record(record), Some(counts)) if record_leaf_count(record) <= MAX_SLOT_DEFAULT_LEAVES => {
1002 counts.entry(format!("{record:?}")).or_insert_with(|| (0, (**record).clone())).0 += 1;
1003 },
1004 _ => *contents = None,
1005 }
1006 },
1007 Self::Leaf { values, .. } => values.entry(format!("{node:?}")).or_insert_with(|| (0, node.clone())).0 += 1,
1008 }
1009 }
1010}
1011
1012pub fn layout_file_name(game_key: &str, campaign: Option<&str>) -> String {
1019 match campaign {
1020 Some(campaign) => format!("{game_key}_{campaign}.ron"),
1021 None => format!("{game_key}.ron"),
1022 }
1023}
1024
1025pub fn child_path(parent: &str, key: &str) -> String {
1028 let name = key.split_once('@').map(|(name, _)| name).unwrap_or(key);
1029 format!("{parent}/{name}")
1030}
1031
1032pub fn variant_path(path: &str, code: u32) -> String {
1039 format!("{path}[{code}]")
1040}
1041
1042fn trim_record(record: &mut RecordNode) {
1044 for node in record.children_mut().iter_mut().flatten() {
1045 match node {
1046 NodeType::Record(child) => trim_record(child),
1047 node => trim_default(node),
1048 }
1049 }
1050}
1051
1052fn trim_default(node: &mut NodeType) {
1054 let len = match node {
1055 NodeType::Unknown26(values) | NodeType::I8Array(values) | NodeType::U8Array(values) => values.len(),
1056 NodeType::BoolArray(values) => values.len(),
1057 NodeType::I16Array(values) | NodeType::AngleArray(values) => values.len(),
1058 NodeType::I32Array(values) => values.value().len(),
1059 NodeType::I64Array(values) => values.len(),
1060 NodeType::U16Array(values) => values.len(),
1061 NodeType::U32Array(values) => values.value().len(),
1062 NodeType::U64Array(values) => values.len(),
1063 NodeType::F32Array(values) => values.len(),
1064 NodeType::F64Array(values) => values.len(),
1065 NodeType::Coord2dArray(values) => values.len(),
1066 NodeType::Coord3dArray(values) => values.len(),
1067 NodeType::Utf16Array(values) | NodeType::AsciiArray(values) => values.len(),
1068 _ => return,
1069 };
1070 if len <= MAX_DEFAULT_ARRAY_LEN {
1071 return;
1072 }
1073
1074 match node {
1075 NodeType::Unknown26(values) | NodeType::I8Array(values) | NodeType::U8Array(values) => values.clear(),
1076 NodeType::BoolArray(values) => values.clear(),
1077 NodeType::I16Array(values) | NodeType::AngleArray(values) => values.clear(),
1078 NodeType::I32Array(values) => values.value_mut().clear(),
1079 NodeType::I64Array(values) => values.clear(),
1080 NodeType::U16Array(values) => values.clear(),
1081 NodeType::U32Array(values) => values.value_mut().clear(),
1082 NodeType::U64Array(values) => values.clear(),
1083 NodeType::F32Array(values) => values.clear(),
1084 NodeType::F64Array(values) => values.clear(),
1085 NodeType::Coord2dArray(values) => values.clear(),
1086 NodeType::Coord3dArray(values) => values.clear(),
1087 NodeType::Utf16Array(values) | NodeType::AsciiArray(values) => values.clear(),
1088 _ => {},
1089 }
1090}
1091
1092fn record_leaf_count(record: &RecordNode) -> usize {
1094 record.children().iter().flatten().map(|node| match node {
1095 NodeType::Record(child) => record_leaf_count(child),
1096 _ => 1,
1097 }).sum()
1098}
1099
1100fn add_missing_slots(slots: &mut Vec<Slot>, fallback: &[Slot]) {
1103 let mut position = 0;
1104 for slot in fallback {
1105 if let Some(own) = slots.iter().position(|own| own.id() == slot.id()) {
1106 position = own + 1;
1107 continue;
1108 }
1109
1110 let mut slot = slot.clone();
1111 match &mut slot {
1112 Slot::Leaf { optional, absent_by_default, .. } => (*optional, *absent_by_default) = (true, true),
1113 Slot::Record { optional, .. } => *optional = true,
1114 }
1115 slots.insert(position, slot);
1116 position += 1;
1117 }
1118}
1119
1120fn collect_startpos(startpos: &ESF, stats: &mut HashMap<String, PathStats>) -> Result<()> {
1122 let NodeType::Record(root) = startpos.root_node() else {
1123 return Err(RLibError::StartposLayoutError("the startpos root is not a record".to_owned()));
1124 };
1125 collect(root, root.name(), stats);
1126 Ok(())
1127}
1128
1129fn collect(record: &RecordNode, path: &str, stats: &mut HashMap<String, PathStats>) {
1131 let groups = record.children().iter().map(|group| group_tokens(group)).collect::<Vec<_>>();
1132 collect_groups(record, path, &groups, stats);
1133
1134 if VARIANT_ARRAYS.contains(&record.name().as_str()) {
1135 for tokens in &groups {
1136 if let Some((None, NodeType::U32(code))) = tokens.first() {
1137 collect_groups(record, &variant_path(path, *code.value()), std::slice::from_ref(tokens), stats);
1138 }
1139 }
1140 }
1141}
1142
1143fn collect_groups(record: &RecordNode, path: &str, groups: &[Vec<(Option<String>, &NodeType)>], stats: &mut HashMap<String, PathStats>) {
1145 let entry = stats.entry(path.to_owned()).or_insert_with(|| PathStats { name: record.name().to_owned(), ..Default::default() });
1146 *entry.versions.entry(*record.version()).or_default() += 1;
1147 *entry.flags.entry(record.record_flags().bits()).or_default() += 1;
1148 entry.array |= record.record_flags().contains(RecordNodeFlags::HAS_NESTED_BLOCKS);
1149 for tokens in groups {
1150 entry.add_group(tokens);
1151 }
1152
1153 for tokens in groups {
1154 for (key, node) in tokens {
1155 if let (Some(key), NodeType::Record(child)) = (key, node) {
1156 collect(child, &child_path(path, key), stats);
1157 }
1158 }
1159 }
1160}
1161
1162fn group_tokens(group: &[NodeType]) -> Vec<(Option<String>, &NodeType)> {
1164 let mut seen = HashMap::<&str, usize>::new();
1165 group.iter().map(|node| match node {
1166 NodeType::Record(record) => {
1167 let count = seen.entry(record.name()).or_default();
1168 *count += 1;
1169 let key = if *count == 1 { record.name().to_owned() } else { format!("{}@{count}", record.name()) };
1170 (Some(key), node)
1171 },
1172 _ => (None, node),
1173 }).collect()
1174}
1175
1176#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1178enum Kind<'a> {
1179 Record(&'a str),
1180 Leaf(Discriminant<NodeType>),
1181}
1182
1183impl SlotStats {
1184
1185 fn kind(&self) -> Kind<'_> {
1187 match self {
1188 Self::Record { key, .. } => Kind::Record(key),
1189 Self::Leaf { kind, .. } => Kind::Leaf(*kind),
1190 }
1191 }
1192}
1193
1194impl Slot {
1195
1196 pub fn id(&self) -> &str {
1198 match self {
1199 Self::Leaf { name, .. } => name,
1200 Self::Record { key, .. } => key,
1201 }
1202 }
1203
1204 fn kind(&self) -> Kind<'_> {
1206 match self {
1207 Self::Record { key, .. } => Kind::Record(key),
1208 Self::Leaf { default, .. } => Kind::Leaf(discriminant(default)),
1209 }
1210 }
1211}
1212
1213fn token_kind<'a>(token: &'a (Option<String>, &NodeType)) -> Kind<'a> {
1215 match token {
1216 (Some(key), _) => Kind::Record(key),
1217 (None, node) => Kind::Leaf(discriminant(*node)),
1218 }
1219}
1220
1221fn align(slots: &[SlotStats], group: &[(Option<String>, &NodeType)]) -> Vec<(usize, usize)> {
1227 let left = slots.iter().map(SlotStats::kind).collect::<Vec<_>>();
1228 let right = group.iter().map(token_kind).collect::<Vec<_>>();
1229 align_kinds(&left, &right)
1230}
1231
1232fn align_kinds(left: &[Kind], right: &[Kind]) -> Vec<(usize, usize)> {
1239 let records = |kinds: &[Kind]| kinds.iter().enumerate().filter(|(_, kind)| matches!(kind, Kind::Record(_))).map(|(index, _)| index).collect::<Vec<_>>();
1240 let (left_records, right_records) = (records(left), records(right));
1241 let record_pairs = longest_common_subsequence(&left_records, &right_records, |&l, &r| left[l] == right[r])
1242 .into_iter()
1243 .map(|(l, r)| (left_records[l], right_records[r]))
1244 .collect::<Vec<_>>();
1245
1246 let mut pairs = vec![];
1248 let mut starts = (0, 0);
1249 for &(l, r) in record_pairs.iter().chain(std::iter::once(&(left.len(), right.len()))) {
1250 let leaves = |kinds: &[Kind], start: usize, end: usize| (start..end).filter(|&index| matches!(kinds[index], Kind::Leaf(_))).collect::<Vec<_>>();
1251 let (left_leaves, right_leaves) = (leaves(left, starts.0, l), leaves(right, starts.1, r));
1252 let same_types = left_leaves.len() == right_leaves.len() && left_leaves.iter().zip(&right_leaves).all(|(&a, &b)| left[a] == right[b]);
1253 if same_types {
1254 pairs.extend(left_leaves.iter().copied().zip(right_leaves.iter().copied()));
1255 } else if left_leaves.len().saturating_mul(right_leaves.len()) > MAX_ALIGNMENT_CELLS {
1256 pairs.extend(align_in_one_pass(&left_leaves, &right_leaves, |&a, &b| left[a] == right[b]));
1257 } else {
1258 pairs.extend(longest_common_subsequence(&left_leaves, &right_leaves, |&a, &b| left[a] == right[b]).into_iter().map(|(a, b)| (left_leaves[a], right_leaves[b])));
1259 }
1260 if l < left.len() {
1261 pairs.push((l, r));
1262 }
1263 starts = (l + 1, r + 1);
1264 }
1265 pairs
1266}
1267
1268fn align_in_one_pass<A, B>(left: &[A], right: &[B], matches: impl Fn(&A, &B) -> bool) -> Vec<(usize, usize)> {
1271 let mut pairs = vec![];
1272 let (mut l, mut r) = (0, 0);
1273 while l < left.len() && r < right.len() {
1274 if matches(&left[l], &right[r]) {
1275 pairs.push((l, r));
1276 l += 1;
1277 r += 1;
1278 } else if left.len() - l >= right.len() - r {
1279 l += 1;
1280 } else {
1281 r += 1;
1282 }
1283 }
1284 pairs
1285}
1286
1287fn longest_common_subsequence<A, B>(left: &[A], right: &[B], matches: impl Fn(&A, &B) -> bool) -> Vec<(usize, usize)> {
1289 let mut lengths = vec![vec![0usize; right.len() + 1]; left.len() + 1];
1290 for l in (0..left.len()).rev() {
1291 for r in (0..right.len()).rev() {
1292 lengths[l][r] = if matches(&left[l], &right[r]) {
1293 lengths[l + 1][r + 1] + 1
1294 } else {
1295 lengths[l + 1][r].max(lengths[l][r + 1])
1296 };
1297 }
1298 }
1299
1300 let mut pairs = vec![];
1301 let (mut l, mut r) = (0, 0);
1302 while l < left.len() && r < right.len() {
1303 if matches(&left[l], &right[r]) {
1304 pairs.push((l, r));
1305 l += 1;
1306 r += 1;
1307 } else if lengths[l + 1][r] >= lengths[l][r + 1] {
1308 l += 1;
1309 } else {
1310 r += 1;
1311 }
1312 }
1313 pairs
1314}
1315
1316fn missing(path: &str) -> RLibError {
1318 RLibError::StartposLayoutError(format!("the startpos layout has no {path}"))
1319}