1use serde_derive::{Deserialize, Serialize};
54
55use std::cmp::Reverse;
56use std::collections::{BTreeMap, HashMap};
57use std::fmt::Debug;
58use std::fs::File;
59use std::io::{BufReader, BufWriter, Write};
60use std::mem::{discriminant, Discriminant};
61use std::ops::RangeInclusive;
62use std::path::Path;
63
64use rpfm_lib::error::{RLibError, Result};
65use rpfm_lib::files::esf::{ESF, NodeType, RecordNode, RecordNodeFlags};
66
67#[cfg(test)] #[path = "layout_test.rs"] mod layout_test;
68
69pub const STARTPOS_LAYOUT_FOLDER: &str = "startpos_layout";
71
72const START_ANCHOR: &str = "^";
74
75pub const MAX_LAYOUT_SLOTS: usize = 256;
77
78const MAX_ALIGNMENT_CELLS: usize = 1 << 20;
80
81const MAX_SLOT_DEFAULT_LEAVES: usize = 16;
83
84const MAX_DEFAULT_ARRAY_LEN: usize = 64;
86
87pub const VARIANT_ARRAYS: [&str; 3] = ["CAI_BDI_POOL_BELIEFS", "CAI_BDI_POOL_DESIRES", "CAI_BDI_POOL_INTENTIONS"];
89
90pub(crate) const MAX_COMPACT_U32: u32 = 0xFF_FFFF;
92
93pub(crate) const COMPACT_I32: RangeInclusive<i32> = -0x80_0000..=0x7F_FFFF;
95
96#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
102pub struct StartposLayout {
103
104 records: BTreeMap<String, RecordLayout>,
106}
107
108#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
110pub struct RecordLayout {
111
112 name: String,
114
115 version: u8,
117
118 flags: u8,
120
121 array: bool,
123
124 #[serde(default)]
126 free_form: bool,
127
128 slots: Vec<Slot>,
130}
131
132#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
134pub enum Slot {
135
136 Leaf {
138
139 name: String,
141
142 default: NodeType,
144
145 #[serde(default)]
147 optional: bool,
148
149 #[serde(default)]
151 absent_by_default: bool,
152 },
153
154 Record {
156
157 key: String,
159
160 optional: bool,
162
163 #[serde(default, skip_serializing_if = "Option::is_none")]
166 default: Option<RecordNode>,
167 },
168}
169
170#[derive(Debug, Clone)]
172pub struct RecordBuilder<'a> {
173 layout: &'a StartposLayout,
174 path: String,
175 record: &'a RecordLayout,
176 groups: Vec<GroupBuilder<'a>>,
177}
178
179#[derive(Debug, Clone)]
181pub struct GroupBuilder<'a> {
182 path: String,
183 record: &'a RecordLayout,
184 values: Vec<Option<NodeType>>,
185}
186
187#[derive(Debug, Clone, Default)]
190pub struct GroupView<'a> {
191 fields: HashMap<&'a str, &'a NodeType>,
192}
193
194#[derive(Debug, Clone)]
196enum SlotStats {
197 Leaf { kind: Discriminant<NodeType>, values: HashMap<String, (usize, NodeType)> },
198 Record { key: String, seen: usize, contents: Option<HashMap<String, (usize, RecordNode)>> },
199}
200
201#[derive(Debug, Clone, Default)]
203struct PathStats {
204 name: String,
205 versions: HashMap<u8, usize>,
206 flags: HashMap<u8, usize>,
207 array: bool,
208 free_form: bool,
209 groups: usize,
210 slots: Vec<SlotStats>,
211}
212
213impl StartposLayout {
218
219 pub fn from_esf(startpos: &ESF) -> Result<Self> {
229 let mut stats = HashMap::new();
230 collect_startpos(startpos, &mut stats)?;
231 Self::from_stats(stats)
232 }
233
234 pub fn from_esfs(startposes: impl IntoIterator<Item = Result<ESF>>) -> Result<Self> {
246 let mut stats = HashMap::new();
247 for startpos in startposes {
248 collect_startpos(&startpos?, &mut stats)?;
249 }
250 Self::from_stats(stats)
251 }
252
253 pub fn with_fallback(mut self, fallback: &StartposLayout) -> Self {
262 for (path, record) in &fallback.records {
263 match self.records.get_mut(path) {
264 Some(own) if own.slots.is_empty() => *own = record.clone(),
265 Some(own) => add_missing_slots(&mut own.slots, &record.slots),
266 None => {
267 self.records.insert(path.to_owned(), record.clone());
268 },
269 }
270 }
271 self
272 }
273
274 fn from_stats(stats: HashMap<String, PathStats>) -> Result<Self> {
276 let records = stats.into_iter().map(|(path, stats)| (path, stats.into_layout())).collect();
277 let mut layout = Self { records };
278 layout.drop_redundant_slot_defaults()?;
279 Ok(layout)
280 }
281
282 fn drop_redundant_slot_defaults(&mut self) -> Result<()> {
284 let mut redundant = vec![];
285 for (path, record) in &self.records {
286 for (index, slot) in record.slots.iter().enumerate() {
287 if let Slot::Record { key, default: Some(default), .. } = slot {
288 if *default == self.default_record(&child_path(path, key))? {
289 redundant.push((path.to_owned(), index));
290 }
291 }
292 }
293 }
294
295 for (path, index) in redundant {
296 if let Some(Slot::Record { default, .. }) = self.records.get_mut(&path).and_then(|record| record.slots.get_mut(index)) {
297 *default = None;
298 }
299 }
300 Ok(())
301 }
302
303 pub fn load(path: &Path) -> Result<Self> {
309 let file = BufReader::new(File::open(path)?);
310 ron::de::from_reader(file).map_err(|error| RLibError::StartposLayoutError(format!("invalid startpos layout: {error}")))
311 }
312
313 pub fn save(&self, path: &Path) -> Result<()> {
319 let mut file = BufWriter::new(File::create(path)?);
320 let config = ron::ser::PrettyConfig::default();
321 let text = ron::ser::to_string_pretty(self, config).map_err(|error| RLibError::StartposLayoutError(format!("can't serialize the startpos layout: {error}")))?;
322 file.write_all(text.as_bytes())?;
323 Ok(())
324 }
325
326 pub fn record(&self, path: &str) -> Option<&RecordLayout> {
328 self.records.get(path)
329 }
330
331 pub fn builder(&self, path: &str) -> Result<RecordBuilder<'_>> {
346
347 static ABSENT: std::sync::OnceLock<RecordLayout> = std::sync::OnceLock::new();
349 let record = self.records.get(path).unwrap_or_else(|| ABSENT.get_or_init(RecordLayout::default));
350 let mut builder = RecordBuilder { layout: self, path: path.to_owned(), record, groups: vec![] };
351 if !record.array {
352 builder.push_group()?;
353 }
354 Ok(builder)
355 }
356
357 pub fn default_record(&self, path: &str) -> Result<RecordNode> {
363 self.builder(path)?.build()
364 }
365
366 pub fn read<'a>(&'a self, path: &str, record: &'a RecordNode) -> Result<Vec<GroupView<'a>>> {
377 let layout = self.record(path).ok_or_else(|| missing(path))?;
378 record.children().iter().enumerate().map(|(index, group)| {
379 let slots = layout.match_children(group).ok_or_else(|| missing(&format!("{path}[{index}]")))?;
380 Ok(GroupView { fields: slots.into_iter().map(Slot::id).zip(group).collect() })
381 }).collect()
382 }
383
384 pub fn read_single<'a>(&'a self, path: &str, record: &'a RecordNode) -> Result<GroupView<'a>> {
395 self.read(path, record)?.into_iter().next().ok_or_else(|| missing(&format!("group in {path}")))
396 }
397
398 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>)>> {
415 let Some(array) = parent.record(array_key) else { return Ok(vec![]) };
416 let item_path = child_path(array_path, item_key);
417 self.read(array_path, array)?.into_iter()
418 .map(|entry| {
419 let item = entry.record(item_key).ok_or_else(|| missing(&item_path))?;
420 Ok((entry.clone(), self.read_single(&item_path, item)?))
421 })
422 .collect()
423 }
424
425 pub fn raw_record(&self, path: &str, children: Vec<NodeType>) -> Result<RecordNode> {
432 let record = self.records.get(path).ok_or_else(|| missing(path))?;
433 let mut builder = RecordBuilder { layout: self, path: path.to_owned(), record, groups: vec![] };
434 builder.push_children(children);
435 builder.build()
436 }
437}
438
439impl RecordLayout {
440
441 pub fn name(&self) -> &str {
443 &self.name
444 }
445
446 pub fn version(&self) -> u8 {
448 self.version
449 }
450
451 pub fn array(&self) -> bool {
453 self.array
454 }
455
456 pub fn free_form(&self) -> bool {
458 self.free_form
459 }
460
461 pub fn slots(&self) -> &[Slot] {
463 &self.slots
464 }
465
466 pub fn has_slot(&self, id: &str) -> bool {
468 self.slots.iter().any(|slot| slot.id() == id)
469 }
470
471 pub fn leaf_default(&self, name: &str) -> Option<&NodeType> {
473 self.slots.iter().find_map(|slot| match slot {
474 Slot::Leaf { name: leaf, default, .. } if leaf == name => Some(default),
475 _ => None,
476 })
477 }
478
479 pub fn match_children(&self, group: &[NodeType]) -> Option<Vec<&Slot>> {
489 if self.free_form {
490 return None;
491 }
492 let tokens = group_tokens(group);
493 let left = self.slots.iter().map(Slot::kind).collect::<Vec<_>>();
494 let right = tokens.iter().map(token_kind).collect::<Vec<_>>();
495 let pairs = align_kinds(&left, &right);
496 (pairs.len() == tokens.len()).then(|| pairs.into_iter().map(|(slot, _)| &self.slots[slot]).collect())
497 }
498
499 fn leaf_position(&self, path: &str, name: &str) -> Result<usize> {
501 self.slots.iter()
502 .position(|slot| matches!(slot, Slot::Leaf { name: slot_name, .. } if slot_name == name))
503 .ok_or_else(|| missing(&format!("{path}:{name}")))
504 }
505
506 fn record_position(&self, path: &str, key: &str) -> Result<usize> {
508 self.slots.iter()
509 .position(|slot| matches!(slot, Slot::Record { key: slot_key, .. } if slot_key == key))
510 .ok_or_else(|| missing(&format!("{path}/{key}")))
511 }
512}
513
514impl<'a> RecordBuilder<'a> {
515
516 pub fn push_group(&mut self) -> Result<&mut GroupBuilder<'a>> {
526 let path = self.path.to_owned();
527 self.push_group_of(path, self.record)
528 }
529
530 pub fn push_variant(&mut self, code: u32) -> Result<&mut GroupBuilder<'a>> {
544 let path = variant_path(&self.path, code);
545 let layout = self.layout;
546 let record = layout.records.get(&path).ok_or_else(|| missing(&path))?;
547 self.push_group_of(path, record)
548 }
549
550 fn push_group_of(&mut self, path: String, record: &'a RecordLayout) -> Result<&mut GroupBuilder<'a>> {
552 let mut values = vec![];
553 for slot in &record.slots {
554 values.push(match slot {
555 Slot::Leaf { absent_by_default: true, .. } => None,
556 Slot::Leaf { default, .. } => Some(default.clone()),
557 Slot::Record { optional: true, .. } => None,
558 Slot::Record { default: Some(default), .. } => Some(NodeType::Record(Box::new(default.clone()))),
559 Slot::Record { key, default: None, .. } => Some(NodeType::Record(Box::new(self.layout.default_record(&child_path(&path, key))?))),
560 });
561 }
562 self.groups.push(GroupBuilder { path, record, values });
563 Ok(self.groups.last_mut().expect("a group was just pushed"))
564 }
565
566 pub fn push_children(&mut self, children: Vec<NodeType>) {
569 let values = children.into_iter().map(Some).collect();
570 self.groups.push(GroupBuilder { path: self.path.to_owned(), record: self.record, values });
571 }
572
573 pub fn group(&mut self) -> Result<&mut GroupBuilder<'a>> {
579 let path = self.path.to_owned();
580 self.groups.first_mut().ok_or_else(|| missing(&format!("{path}[0]")))
581 }
582
583 pub fn build(self) -> Result<RecordNode> {
589 let mut record = RecordNode::default();
590 record.set_name(self.record.name.to_owned());
591 record.set_version(self.record.version);
592 record.set_record_flags(RecordNodeFlags::from_bits_truncate(self.record.flags));
593 record.set_children(self.groups.into_iter().map(|group| group.values.into_iter().flatten().collect()).collect());
594 Ok(record)
595 }
596}
597
598impl GroupBuilder<'_> {
599
600 pub fn set(&mut self, name: &str, value: NodeType) -> Result<&mut Self> {
611 if !self.record.has_slot(name) {
612 return Ok(self);
613 }
614 let position = self.record.leaf_position(&self.path, name)?;
615 let Some(Slot::Leaf { default, .. }) = self.record.slots.get(position) else { unreachable!("leaf_position returns leaf slots") };
616 if discriminant(default) != discriminant(&value) {
617 return Err(RLibError::StartposLayoutError(format!("{}:{name}: expected a value like {default:?}, got {value:?}", self.path)));
618 }
619 self.values[position] = Some(value);
620 Ok(self)
621 }
622
623 pub fn set_u32(&mut self, name: &str, value: u32) -> Result<&mut Self> {
632 if !self.record.has_slot(name) {
633 return Ok(self);
634 }
635 let mut node = self.default_of(name)?;
636 match &mut node {
637 NodeType::U32(node) => { node.set_value(value).set_optimized(value <= MAX_COMPACT_U32); },
638 NodeType::U16(node) => { *node = u16::try_from(value).map_err(|_| self.wrong_type(name, &value))?; },
639 NodeType::U8(node) => { *node = u8::try_from(value).map_err(|_| self.wrong_type(name, &value))?; },
640 _ => return Err(self.wrong_type(name, &value)),
641 }
642 self.set(name, node)
643 }
644
645 pub fn set_i32(&mut self, name: &str, value: i32) -> Result<&mut Self> {
651 if !self.record.has_slot(name) {
652 return Ok(self);
653 }
654 let mut node = self.default_of(name)?;
655 match &mut node {
656 NodeType::I32(node) => { node.set_value(value).set_optimized(COMPACT_I32.contains(&value)); },
657 NodeType::I16(node) => { *node = i16::try_from(value).map_err(|_| self.wrong_type(name, &value))?; },
658 NodeType::I8(node) => { *node = i8::try_from(value).map_err(|_| self.wrong_type(name, &value))?; },
659 _ => return Err(self.wrong_type(name, &value)),
660 }
661 self.set(name, node)
662 }
663
664 pub fn set_f32(&mut self, name: &str, value: f32) -> Result<&mut Self> {
670 if !self.record.has_slot(name) {
671 return Ok(self);
672 }
673 let mut node = self.default_of(name)?;
674 match &mut node {
675 NodeType::F32(node) => { node.set_value(value).set_optimized(value == 0.0); },
676 _ => return Err(self.wrong_type(name, &value)),
677 }
678 self.set(name, node)
679 }
680
681 pub fn set_bool(&mut self, name: &str, value: bool) -> Result<&mut Self> {
687 if !self.record.has_slot(name) {
688 return Ok(self);
689 }
690 let mut node = self.default_of(name)?;
691 match &mut node {
692 NodeType::Bool(node) => { node.set_value(value); },
693 _ => return Err(self.wrong_type(name, &value)),
694 }
695 self.set(name, node)
696 }
697
698 pub fn set_str(&mut self, name: &str, value: &str) -> Result<&mut Self> {
704 if !self.record.has_slot(name) {
705 return Ok(self);
706 }
707 let mut node = self.default_of(name)?;
708 match &mut node {
709 NodeType::Ascii(node) | NodeType::Utf16(node) => { *node = value.to_owned(); },
710 _ => return Err(self.wrong_type(name, &value)),
711 }
712 self.set(name, node)
713 }
714
715 pub fn push_child(&mut self, node: NodeType) -> Result<&mut Self> {
721 if !self.record.free_form {
722 return Err(RLibError::StartposLayoutError(format!("{} has a fixed layout", self.path)));
723 }
724 self.values.push(Some(node));
725 Ok(self)
726 }
727
728 pub fn set_strings(&mut self, name: &str, values: Vec<String>) -> Result<&mut Self> {
734 if !self.record.has_slot(name) {
735 return Ok(self);
736 }
737 let mut node = self.default_of(name)?;
738 match &mut node {
739 NodeType::AsciiArray(node) | NodeType::Utf16Array(node) => { *node = values; },
740 _ => return Err(self.wrong_type(name, &values)),
741 }
742 self.set(name, node)
743 }
744
745 pub fn set_bytes(&mut self, name: &str, values: Vec<u8>) -> Result<&mut Self> {
751 if !self.record.has_slot(name) {
752 return Ok(self);
753 }
754 let mut node = self.default_of(name)?;
755 match &mut node {
756 NodeType::U8Array(node) | NodeType::I8Array(node) => { *node = values; },
757 _ => return Err(self.wrong_type(name, &values)),
758 }
759 self.set(name, node)
760 }
761
762 pub fn set_u32s(&mut self, name: &str, values: Vec<u32>) -> Result<&mut Self> {
769 if !self.record.has_slot(name) {
770 return Ok(self);
771 }
772 let mut node = self.default_of(name)?;
773 match &mut node {
774 NodeType::U32Array(node) => {
775 let optimized = !values.is_empty() && values.iter().all(|value| *value <= MAX_COMPACT_U32);
776 node.set_value(values).set_optimized(optimized);
777 },
778 _ => return Err(self.wrong_type(name, &values)),
779 }
780 self.set(name, node)
781 }
782
783 pub fn unset(&mut self, name: &str) -> Result<&mut Self> {
789 if !self.record.has_slot(name) {
790 return Ok(self);
791 }
792 let position = self.record.leaf_position(&self.path, name)?;
793 if !matches!(self.record.slots[position], Slot::Leaf { optional: true, .. }) {
794 return Err(RLibError::StartposLayoutError(format!("{}:{name} is always present", self.path)));
795 }
796 self.values[position] = None;
797 Ok(self)
798 }
799
800 pub fn set_record(&mut self, key: &str, record: Option<RecordNode>) -> Result<&mut Self> {
811 if !self.record.has_slot(key) {
812 return Ok(self);
813 }
814 let position = self.record.record_position(&self.path, key)?;
815 if record.is_none() && matches!(self.record.slots[position], Slot::Record { optional: false, .. }) {
816 return Err(RLibError::StartposLayoutError(format!("{}/{key} is always present", self.path)));
817 }
818 self.values[position] = record.map(|record| NodeType::Record(Box::new(record)));
819 Ok(self)
820 }
821
822 fn default_of(&self, name: &str) -> Result<NodeType> {
824 match &self.record.slots[self.record.leaf_position(&self.path, name)?] {
825 Slot::Leaf { default, .. } => Ok(default.clone()),
826 Slot::Record { .. } => unreachable!("leaf_position returns leaf slots"),
827 }
828 }
829
830 fn wrong_type(&self, name: &str, value: &dyn Debug) -> RLibError {
832 RLibError::StartposLayoutError(format!("{}:{name}: {value:?} doesn't fit the field", self.path))
833 }
834}
835
836impl<'a> GroupView<'a> {
837
838 pub fn ids(&self) -> impl Iterator<Item = &'a str> + '_ {
840 self.fields.keys().copied()
841 }
842
843 pub fn get(&self, id: &str) -> Option<&'a NodeType> {
845 self.fields.get(id).copied()
846 }
847
848 pub fn u32(&self, name: &str) -> Option<u32> {
850 match self.get(name)? {
851 NodeType::U32(node) => Some(*node.value()),
852 NodeType::U16(value) => Some(u32::from(*value)),
853 NodeType::U8(value) => Some(u32::from(*value)),
854 _ => None,
855 }
856 }
857
858 pub fn i32(&self, name: &str) -> Option<i32> {
860 match self.get(name)? {
861 NodeType::I32(node) => Some(*node.value()),
862 NodeType::I16(value) => Some(i32::from(*value)),
863 NodeType::I8(value) => Some(i32::from(*value)),
864 _ => None,
865 }
866 }
867
868 pub fn bool(&self, name: &str) -> Option<bool> {
870 match self.get(name)? {
871 NodeType::Bool(node) => Some(*node.value()),
872 _ => None,
873 }
874 }
875
876 pub fn str(&self, name: &str) -> Option<&'a str> {
878 match self.get(name)? {
879 NodeType::Ascii(value) | NodeType::Utf16(value) => Some(value),
880 _ => None,
881 }
882 }
883
884 pub fn f32(&self, name: &str) -> Option<f32> {
886 match self.get(name)? {
887 NodeType::F32(node) => Some(*node.value()),
888 _ => None,
889 }
890 }
891
892 pub fn strs(&self, name: &str) -> Option<&'a [String]> {
894 match self.get(name)? {
895 NodeType::AsciiArray(values) | NodeType::Utf16Array(values) => Some(values),
896 _ => None,
897 }
898 }
899
900 pub fn u32s(&self, name: &str) -> Option<&'a [u32]> {
902 match self.get(name)? {
903 NodeType::U32Array(node) => Some(node.value()),
904 _ => None,
905 }
906 }
907
908 pub fn record(&self, key: &str) -> Option<&'a RecordNode> {
910 match self.get(key)? {
911 NodeType::Record(record) => Some(record),
912 _ => None,
913 }
914 }
915}
916
917impl PathStats {
918
919 fn add_group(&mut self, group: &[(Option<String>, &NodeType)]) {
921 self.groups += 1;
922 if self.free_form {
923 return;
924 }
925
926 let same_shape = group.len() == self.slots.len() && group.iter().zip(&self.slots).all(|(token, slot)| token_kind(token) == slot.kind());
928 if same_shape {
929 for (slot, token) in self.slots.iter_mut().zip(group) {
930 slot.record(token.1);
931 }
932 return;
933 }
934
935 let alignment = align(&self.slots, group);
937 let slot_count = self.slots.len();
938 let mut old_slots = std::mem::take(&mut self.slots).into_iter();
939 let mut merged = Vec::with_capacity(slot_count.max(group.len()));
940 let mut slot_index = 0;
941 let mut group_index = 0;
942 let mut pairs = alignment.into_iter().peekable();
943 while slot_index < slot_count || group_index < group.len() {
944 match pairs.peek().copied() {
945 Some((slot, token)) if slot == slot_index && token == group_index => {
946 let mut stats = old_slots.next().expect("slots are consumed in order");
947 stats.record(group[token].1);
948 merged.push(stats);
949 slot_index += 1;
950 group_index += 1;
951 pairs.next();
952 },
953 Some((slot, _)) if slot_index < slot => {
954 merged.push(old_slots.next().expect("slots are consumed in order"));
955 slot_index += 1;
956 },
957 Some(_) => {
958 merged.push(SlotStats::new(&group[group_index]));
959 group_index += 1;
960 },
961 None if slot_index < slot_count => {
962 merged.push(old_slots.next().expect("slots are consumed in order"));
963 slot_index += 1;
964 },
965 None => {
966 merged.push(SlotStats::new(&group[group_index]));
967 group_index += 1;
968 },
969 }
970 }
971 self.slots = merged;
972 if self.slots.len() > MAX_LAYOUT_SLOTS {
973 self.free_form = true;
974 self.slots.clear();
975 }
976 }
977
978 fn into_layout(self) -> RecordLayout {
980 let mut slots = vec![];
981 let mut anchor = START_ANCHOR.to_owned();
982 let mut offset = 0;
983 for slot in self.slots {
984 match slot {
985 SlotStats::Record { key, seen, contents } => {
986 let optional = seen < self.groups;
987 if !optional {
988 anchor = key.to_owned();
989 offset = 0;
990 }
991
992 let default = contents.and_then(|contents| contents.into_iter()
994 .max_by(|(text_a, (count_a, _)), (text_b, (count_b, _))| count_a.cmp(count_b).then_with(|| text_b.cmp(text_a)))
995 .map(|(_, (_, mut record))| {
996 trim_record(&mut record);
997 record
998 }));
999 slots.push(Slot::Record { key, optional, default });
1000 },
1001 SlotStats::Leaf { values, .. } => {
1002 let seen = values.values().map(|(count, _)| count).sum::<usize>();
1003 let optional = seen < self.groups;
1004 let absent_by_default = seen * 2 < self.groups;
1005 offset += 1;
1006
1007 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");
1009 trim_default(&mut default);
1010 slots.push(Slot::Leaf { name: format!("{anchor}+{offset}"), default, optional, absent_by_default });
1011 },
1012 }
1013 }
1014
1015 let most_common = |counts: HashMap<u8, usize>| counts.into_iter().max_by_key(|&(value, count)| (count, Reverse(value))).map(|(value, _)| value).unwrap_or_default();
1016 RecordLayout {
1017 name: self.name,
1018 version: most_common(self.versions),
1019 flags: most_common(self.flags),
1020 array: self.array,
1021 free_form: self.free_form,
1022 slots,
1023 }
1024 }
1025}
1026
1027impl SlotStats {
1028
1029 fn new(token: &(Option<String>, &NodeType)) -> Self {
1031 match token {
1032 (Some(key), node) => {
1033 let mut stats = Self::Record { key: key.to_owned(), seen: 0, contents: Some(HashMap::new()) };
1034 stats.record(node);
1035 stats
1036 },
1037 (None, node) => {
1038 let mut values = HashMap::new();
1039 values.insert(format!("{node:?}"), (1, (*node).clone()));
1040 Self::Leaf { kind: discriminant(*node), values }
1041 },
1042 }
1043 }
1044
1045 fn record(&mut self, node: &NodeType) {
1047 match self {
1048 Self::Record { seen, contents, .. } => {
1049 *seen += 1;
1050
1051 match (node, contents.as_mut()) {
1053 (NodeType::Record(record), Some(counts)) if record_leaf_count(record) <= MAX_SLOT_DEFAULT_LEAVES => {
1054 counts.entry(format!("{record:?}")).or_insert_with(|| (0, (**record).clone())).0 += 1;
1055 },
1056 _ => *contents = None,
1057 }
1058 },
1059 Self::Leaf { values, .. } => values.entry(format!("{node:?}")).or_insert_with(|| (0, node.clone())).0 += 1,
1060 }
1061 }
1062}
1063
1064pub fn layout_file_name(game_key: &str, campaign: Option<&str>) -> String {
1071 match campaign {
1072 Some(campaign) => format!("{game_key}_{campaign}.ron"),
1073 None => format!("{game_key}.ron"),
1074 }
1075}
1076
1077pub fn child_path(parent: &str, key: &str) -> String {
1080 let name = key.split_once('@').map(|(name, _)| name).unwrap_or(key);
1081 format!("{parent}/{name}")
1082}
1083
1084pub fn variant_path(path: &str, code: u32) -> String {
1091 format!("{path}[{code}]")
1092}
1093
1094fn trim_record(record: &mut RecordNode) {
1096 for node in record.children_mut().iter_mut().flatten() {
1097 match node {
1098 NodeType::Record(child) => trim_record(child),
1099 node => trim_default(node),
1100 }
1101 }
1102}
1103
1104fn trim_default(node: &mut NodeType) {
1106 let len = match node {
1107 NodeType::Unknown26(values) | NodeType::I8Array(values) | NodeType::U8Array(values) => values.len(),
1108 NodeType::BoolArray(values) => values.len(),
1109 NodeType::I16Array(values) | NodeType::AngleArray(values) => values.len(),
1110 NodeType::I32Array(values) => values.value().len(),
1111 NodeType::I64Array(values) => values.len(),
1112 NodeType::U16Array(values) => values.len(),
1113 NodeType::U32Array(values) => values.value().len(),
1114 NodeType::U64Array(values) => values.len(),
1115 NodeType::F32Array(values) => values.len(),
1116 NodeType::F64Array(values) => values.len(),
1117 NodeType::Coord2dArray(values) => values.len(),
1118 NodeType::Coord3dArray(values) => values.len(),
1119 NodeType::Utf16Array(values) | NodeType::AsciiArray(values) => values.len(),
1120 _ => return,
1121 };
1122 if len <= MAX_DEFAULT_ARRAY_LEN {
1123 return;
1124 }
1125
1126 match node {
1127 NodeType::Unknown26(values) | NodeType::I8Array(values) | NodeType::U8Array(values) => values.clear(),
1128 NodeType::BoolArray(values) => values.clear(),
1129 NodeType::I16Array(values) | NodeType::AngleArray(values) => values.clear(),
1130 NodeType::I32Array(values) => values.value_mut().clear(),
1131 NodeType::I64Array(values) => values.clear(),
1132 NodeType::U16Array(values) => values.clear(),
1133 NodeType::U32Array(values) => values.value_mut().clear(),
1134 NodeType::U64Array(values) => values.clear(),
1135 NodeType::F32Array(values) => values.clear(),
1136 NodeType::F64Array(values) => values.clear(),
1137 NodeType::Coord2dArray(values) => values.clear(),
1138 NodeType::Coord3dArray(values) => values.clear(),
1139 NodeType::Utf16Array(values) | NodeType::AsciiArray(values) => values.clear(),
1140 _ => {},
1141 }
1142}
1143
1144fn record_leaf_count(record: &RecordNode) -> usize {
1146 record.children().iter().flatten().map(|node| match node {
1147 NodeType::Record(child) => record_leaf_count(child),
1148 _ => 1,
1149 }).sum()
1150}
1151
1152fn add_missing_slots(slots: &mut Vec<Slot>, fallback: &[Slot]) {
1155 let mut position = 0;
1156 for slot in fallback {
1157 if let Some(own) = slots.iter().position(|own| own.id() == slot.id()) {
1158 position = own + 1;
1159 continue;
1160 }
1161
1162 let mut slot = slot.clone();
1163 match &mut slot {
1164 Slot::Leaf { optional, absent_by_default, .. } => (*optional, *absent_by_default) = (true, true),
1165 Slot::Record { optional, .. } => *optional = true,
1166 }
1167 slots.insert(position, slot);
1168 position += 1;
1169 }
1170}
1171
1172fn collect_startpos(startpos: &ESF, stats: &mut HashMap<String, PathStats>) -> Result<()> {
1174 let NodeType::Record(root) = startpos.root_node() else {
1175 return Err(RLibError::StartposLayoutError("the startpos root is not a record".to_owned()));
1176 };
1177 collect(root, root.name(), stats);
1178 Ok(())
1179}
1180
1181fn collect(record: &RecordNode, path: &str, stats: &mut HashMap<String, PathStats>) {
1183 let groups = record.children().iter().map(|group| group_tokens(group)).collect::<Vec<_>>();
1184 collect_groups(record, path, &groups, stats);
1185
1186 if VARIANT_ARRAYS.contains(&record.name().as_str()) {
1187 for tokens in &groups {
1188 if let Some((None, NodeType::U32(code))) = tokens.first() {
1189 collect_groups(record, &variant_path(path, *code.value()), std::slice::from_ref(tokens), stats);
1190 }
1191 }
1192 }
1193}
1194
1195fn collect_groups(record: &RecordNode, path: &str, groups: &[Vec<(Option<String>, &NodeType)>], stats: &mut HashMap<String, PathStats>) {
1197 let entry = stats.entry(path.to_owned()).or_insert_with(|| PathStats { name: record.name().to_owned(), ..Default::default() });
1198 *entry.versions.entry(*record.version()).or_default() += 1;
1199 *entry.flags.entry(record.record_flags().bits()).or_default() += 1;
1200 entry.array |= record.record_flags().contains(RecordNodeFlags::HAS_NESTED_BLOCKS);
1201 for tokens in groups {
1202 entry.add_group(tokens);
1203 }
1204
1205 for tokens in groups {
1206 for (key, node) in tokens {
1207 if let (Some(key), NodeType::Record(child)) = (key, node) {
1208 collect(child, &child_path(path, key), stats);
1209 }
1210 }
1211 }
1212}
1213
1214fn group_tokens(group: &[NodeType]) -> Vec<(Option<String>, &NodeType)> {
1216 let mut seen = HashMap::<&str, usize>::new();
1217 group.iter().map(|node| match node {
1218 NodeType::Record(record) => {
1219 let count = seen.entry(record.name()).or_default();
1220 *count += 1;
1221 let key = if *count == 1 { record.name().to_owned() } else { format!("{}@{count}", record.name()) };
1222 (Some(key), node)
1223 },
1224 _ => (None, node),
1225 }).collect()
1226}
1227
1228#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1230enum Kind<'a> {
1231 Record(&'a str),
1232 Leaf(Discriminant<NodeType>),
1233}
1234
1235impl SlotStats {
1236
1237 fn kind(&self) -> Kind<'_> {
1239 match self {
1240 Self::Record { key, .. } => Kind::Record(key),
1241 Self::Leaf { kind, .. } => Kind::Leaf(*kind),
1242 }
1243 }
1244}
1245
1246impl Slot {
1247
1248 pub fn id(&self) -> &str {
1250 match self {
1251 Self::Leaf { name, .. } => name,
1252 Self::Record { key, .. } => key,
1253 }
1254 }
1255
1256 fn kind(&self) -> Kind<'_> {
1258 match self {
1259 Self::Record { key, .. } => Kind::Record(key),
1260 Self::Leaf { default, .. } => Kind::Leaf(discriminant(default)),
1261 }
1262 }
1263}
1264
1265fn token_kind<'a>(token: &'a (Option<String>, &NodeType)) -> Kind<'a> {
1267 match token {
1268 (Some(key), _) => Kind::Record(key),
1269 (None, node) => Kind::Leaf(discriminant(*node)),
1270 }
1271}
1272
1273fn align(slots: &[SlotStats], group: &[(Option<String>, &NodeType)]) -> Vec<(usize, usize)> {
1279 let left = slots.iter().map(SlotStats::kind).collect::<Vec<_>>();
1280 let right = group.iter().map(token_kind).collect::<Vec<_>>();
1281 align_kinds(&left, &right)
1282}
1283
1284fn align_kinds(left: &[Kind], right: &[Kind]) -> Vec<(usize, usize)> {
1291 let records = |kinds: &[Kind]| kinds.iter().enumerate().filter(|(_, kind)| matches!(kind, Kind::Record(_))).map(|(index, _)| index).collect::<Vec<_>>();
1292 let (left_records, right_records) = (records(left), records(right));
1293 let record_pairs = longest_common_subsequence(&left_records, &right_records, |&l, &r| left[l] == right[r])
1294 .into_iter()
1295 .map(|(l, r)| (left_records[l], right_records[r]))
1296 .collect::<Vec<_>>();
1297
1298 let mut pairs = vec![];
1300 let mut starts = (0, 0);
1301 for &(l, r) in record_pairs.iter().chain(std::iter::once(&(left.len(), right.len()))) {
1302 let leaves = |kinds: &[Kind], start: usize, end: usize| (start..end).filter(|&index| matches!(kinds[index], Kind::Leaf(_))).collect::<Vec<_>>();
1303 let (left_leaves, right_leaves) = (leaves(left, starts.0, l), leaves(right, starts.1, r));
1304 let same_types = left_leaves.len() == right_leaves.len() && left_leaves.iter().zip(&right_leaves).all(|(&a, &b)| left[a] == right[b]);
1305 if same_types {
1306 pairs.extend(left_leaves.iter().copied().zip(right_leaves.iter().copied()));
1307 } else if left_leaves.len().saturating_mul(right_leaves.len()) > MAX_ALIGNMENT_CELLS {
1308 pairs.extend(align_in_one_pass(&left_leaves, &right_leaves, |&a, &b| left[a] == right[b]));
1309 } else {
1310 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])));
1311 }
1312 if l < left.len() {
1313 pairs.push((l, r));
1314 }
1315 starts = (l + 1, r + 1);
1316 }
1317 pairs
1318}
1319
1320fn align_in_one_pass<A, B>(left: &[A], right: &[B], matches: impl Fn(&A, &B) -> bool) -> Vec<(usize, usize)> {
1323 let mut pairs = vec![];
1324 let (mut l, mut r) = (0, 0);
1325 while l < left.len() && r < right.len() {
1326 if matches(&left[l], &right[r]) {
1327 pairs.push((l, r));
1328 l += 1;
1329 r += 1;
1330 } else if left.len() - l >= right.len() - r {
1331 l += 1;
1332 } else {
1333 r += 1;
1334 }
1335 }
1336 pairs
1337}
1338
1339fn longest_common_subsequence<A, B>(left: &[A], right: &[B], matches: impl Fn(&A, &B) -> bool) -> Vec<(usize, usize)> {
1341 let mut lengths = vec![vec![0usize; right.len() + 1]; left.len() + 1];
1342 for l in (0..left.len()).rev() {
1343 for r in (0..right.len()).rev() {
1344 lengths[l][r] = if matches(&left[l], &right[r]) {
1345 lengths[l + 1][r + 1] + 1
1346 } else {
1347 lengths[l + 1][r].max(lengths[l][r + 1])
1348 };
1349 }
1350 }
1351
1352 let mut pairs = vec![];
1353 let (mut l, mut r) = (0, 0);
1354 while l < left.len() && r < right.len() {
1355 if matches(&left[l], &right[r]) {
1356 pairs.push((l, r));
1357 l += 1;
1358 r += 1;
1359 } else if lengths[l + 1][r] >= lengths[l][r + 1] {
1360 l += 1;
1361 } else {
1362 r += 1;
1363 }
1364 }
1365 pairs
1366}
1367
1368fn missing(path: &str) -> RLibError {
1370 RLibError::StartposLayoutError(format!("the startpos layout has no {path}"))
1371}