1use crate::crc::type_id;
8use crate::crs::{self, Reader};
9use crate::error::{format_err, Error, Result};
10
11type Ref = Option<String>;
12
13fn refs(r: &mut Reader) -> Result<Vec<Ref>> {
14 (0..r.count()?).map(|_| r.external_ref()).collect()
15}
16
17fn strings<const N: usize>(r: &mut Reader) -> Result<[String; N]> {
18 let mut out: [String; N] = std::array::from_fn(|_| String::new());
19 for s in &mut out {
20 *s = r.string()?;
21 }
22 Ok(out)
23}
24
25fn i32s<const N: usize>(r: &mut Reader) -> Result<[i32; N]> {
26 let mut out = [0; N];
27 for v in &mut out {
28 *v = r.i32()?;
29 }
30 Ok(out)
31}
32
33pub type Alignment = u32;
35
36pub mod flags {
38 pub const HIDDEN: u32 = 1 << 0;
40 pub const FILL_WIDTH: u32 = 1 << 2;
42 pub const FILL_HEIGHT: u32 = 1 << 3;
44 pub const SCREEN_HEIGHT: u32 = 1 << 4;
46 pub const SCREEN_WIDTH: u32 = 1 << 5;
48 pub const DIM: u32 = 1 << 18;
50 pub const MOUSE: u32 = 1 << 16;
52 pub const LOCKED: u32 = 1 << 24;
56}
57
58#[derive(Debug, Clone, Default)]
62pub struct ElementBase {
63 pub margins: [i32; 4],
65 pub min_size: [i32; 2],
67 pub alignment: Alignment,
69 pub flags: u32,
71 pub color: u32,
73 pub cursor: i32,
78 pub border: Ref,
80 pub tooltip_dialog: Ref,
82 pub string_table: Ref,
84 pub tooltip: String,
86}
87
88impl ElementBase {
89 fn read(r: &mut Reader) -> Result<Self> {
90 r.block(|r, _| {
91 let margins = i32s(r)?;
92 let min_size = i32s(r)?;
93 let alignment = r.u32()?;
94 let flags = r.u32()?;
95 let color = r.u32()?;
96 let cursor = r.i32()?;
97 Ok(ElementBase {
98 margins,
99 min_size,
100 alignment,
101 flags,
102 color,
103 cursor,
104 border: r.external_ref()?,
105 tooltip_dialog: r.external_ref()?,
106 string_table: r.external_ref()?,
107 tooltip: r.string()?,
108 })
109 })
110 }
111}
112
113#[derive(Debug, Clone)]
115pub struct Button {
116 pub states: [Ref; 4],
118 pub content: Ref,
120 pub colors: [u32; 4],
122}
123
124#[derive(Debug, Clone)]
126pub struct Text {
127 pub font: Ref,
129 pub string_table: Ref,
131 pub text: String,
133 pub shadow: bool,
135 pub wrap: bool,
137 pub max_width: i32,
139 pub justify: u32,
141 pub ellipsis_width: i32,
143}
144
145#[derive(Debug, Clone)]
147pub struct Label {
148 pub text: Text,
150 pub sprite_sheet: Ref,
152 pub sprite: String,
154 pub text_alignment: u32,
156 pub orientation: u32,
158 pub min_text_width: i32,
160 pub gap: i32,
162 pub icon_size: [i32; 2],
164}
165
166#[derive(Debug, Clone)]
168pub enum ElementKind {
169 Group {
171 children: Vec<Ref>,
173 },
174 Page {
176 children: Vec<Ref>,
178 page: i32,
180 },
181 Radio {
183 children: Vec<Ref>,
185 selected: i32,
187 },
188 Ribbon {
190 vertical: bool,
192 uniform: bool,
194 spacing: i32,
196 children: Vec<Ref>,
198 },
199 Border {
201 sprite_sheet: Ref,
203 sprites: [String; 9],
205 },
206 Image {
208 sprite_sheet: Ref,
210 sprite: String,
212 size: [i32; 2],
214 },
215 FrustumOutline {
220 sprite_sheet: Ref,
222 sprite: String,
224 size: [i32; 2],
226 },
227 AnimatedImage {
229 sprite_sheet: Ref,
231 sprite: String,
233 size: [i32; 2],
235 frames: Vec<String>,
237 rate: i32,
239 },
240 Button(Button),
242 Check {
244 button: Button,
246 check: Ref,
248 },
249 Combo {
251 button: Button,
253 refs: [Ref; 3],
256 },
257 Text(Text),
259 Label(Label),
261 ClipRegion {
263 unknown: [i32; 2],
265 content: Ref,
267 },
268 Container {
270 unknown: [i32; 3],
272 refs: [Ref; 3],
274 unknown_bools: [bool; 2],
276 },
277 Edit {
279 font: Ref,
281 text: String,
283 max_length: i32,
285 unknown_bool: bool,
287 unknown_string: String,
289 },
290 Graph {
292 unknown: [i32; 3],
294 unknown_u32: u32,
296 sprite_sheet: Ref,
298 sprite: String,
300 },
301 Layout {
303 items: Vec<LayoutItem>,
305 },
306 List {
308 refs: [Ref; 2],
310 sprite_sheet: Ref,
312 sprite: String,
314 unknown: [u32; 2],
316 },
317 NumberEdit {
319 font: Ref,
321 unknown: [i32; 4],
323 unknown_bool: bool,
325 },
326 Number {
328 font: Ref,
330 color: u32,
332 unknown_int: i32,
334 unknown_float: f32,
336 unknown_bool: bool,
338 },
339 Slider {
341 track: Ref,
343 sprite_sheet: Ref,
345 sprites: [String; 5],
347 unknown: [i32; 2],
349 range: [f32; 2],
351 unknown_b0: i32,
353 },
354 Spinner {
356 refs: [Ref; 3],
358 unknown: i32,
360 },
361 Tab {
363 refs: [Ref; 3],
365 sprite_sheet: Ref,
367 sprites: [String; 3],
369 unknown: i32,
371 tabs: Vec<(Ref, Ref)>,
373 },
374 Progress {
376 color: u32,
378 sprite_sheet: Ref,
380 sprites: [String; 6],
382 unknown_ref: Ref,
384 },
385 Scroll {
387 sprite_sheet: Ref,
389 sprites: [String; 9],
391 vertical: bool,
393 unknown: i32,
395 unknown_u32: [u32; 2],
397 },
398 Table {
400 unknown: [i32; 3],
402 cells: Vec<TableCell>,
404 },
405 DocumentText(DocumentText),
407}
408
409#[derive(Debug, Clone)]
413pub struct DocumentText {
414 pub string_table: Ref,
416 pub text: String,
418 pub sprite_sheet: Ref,
420 pub bullets: Vec<String>,
422 pub underline_sprite: String,
424 pub link_colors: [u32; 3],
426 pub indent: i32,
428 pub width: i32,
430 pub fonts: Vec<Ref>,
432 pub sheets: Vec<Ref>,
434 pub colors: Vec<u32>,
436 pub images: Ref,
438}
439
440#[derive(Debug, Clone)]
442pub struct LayoutItem {
443 pub element: Ref,
445 pub strings: [String; 3],
447 pub unknown_u32: [u32; 2],
449 pub unknown_i32: [i32; 2],
451 pub flag: bool,
453}
454
455#[derive(Debug, Clone)]
457pub struct TableCell {
458 pub column: i32,
460 pub row: i32,
462 pub element: Ref,
464}
465
466#[derive(Debug, Clone)]
468pub struct Element {
469 pub class: &'static str,
471 pub base: ElementBase,
473 pub kind: ElementKind,
475}
476
477impl Element {
478 pub fn children(&self) -> &[Ref] {
480 match &self.kind {
481 ElementKind::Group { children }
482 | ElementKind::Page { children, .. }
483 | ElementKind::Radio { children, .. }
484 | ElementKind::Ribbon { children, .. } => children,
485 _ => &[],
486 }
487 }
488}
489
490fn read_button(r: &mut Reader) -> Result<Button> {
491 r.block(|r, _| {
492 let mut states: [Ref; 4] = Default::default();
493 for s in &mut states {
494 *s = r.external_ref()?;
495 }
496 let content = r.external_ref()?;
497 let colors = [r.u32()?, r.u32()?, r.u32()?, r.u32()?];
498 Ok(Button {
499 states,
500 content,
501 colors,
502 })
503 })
504}
505
506fn read_text(r: &mut Reader) -> Result<Text> {
507 r.block(|r, _| {
508 Ok(Text {
509 font: r.external_ref()?,
510 string_table: r.external_ref()?,
511 text: r.string()?,
512 shadow: r.bool()?,
513 wrap: r.bool()?,
514 max_width: r.i32()?,
515 justify: r.u32()?,
516 ellipsis_width: r.i32()?,
517 })
518 })
519}
520
521fn read_group(r: &mut Reader) -> Result<Vec<Ref>> {
522 r.block(|r, _| refs(r))
523}
524
525fn read_image(r: &mut Reader) -> Result<(Ref, String, [i32; 2])> {
526 r.block(|r, _| Ok((r.external_ref()?, r.string()?, i32s(r)?)))
527}
528
529fn read_element(class: &'static str, r: &mut Reader) -> Result<Element> {
531 let base = ElementBase::read(r)?;
532 let kind = match class {
533 "GroupDescription" => ElementKind::Group {
534 children: read_group(r)?,
535 },
536 "PageDescription" => {
537 let children = read_group(r)?;
538 ElementKind::Page {
539 children,
540 page: r.block(|r, _| r.i32())?,
541 }
542 }
543 "RadioDescription" => {
544 let children = read_group(r)?;
545 ElementKind::Radio {
546 children,
547 selected: r.block(|r, _| r.i32())?,
548 }
549 }
550 "RibbonDescription" => r.block(|r, _| {
551 Ok(ElementKind::Ribbon {
552 vertical: r.bool()?,
553 uniform: r.bool()?,
554 spacing: r.i32()?,
555 children: refs(r)?,
556 })
557 })?,
558 "BorderDescription" => r.block(|r, _| {
559 Ok(ElementKind::Border {
560 sprite_sheet: r.external_ref()?,
561 sprites: strings(r)?,
562 })
563 })?,
564 "ImageDescription" => {
565 let (sprite_sheet, sprite, size) = read_image(r)?;
566 ElementKind::Image {
567 sprite_sheet,
568 sprite,
569 size,
570 }
571 }
572 "FrustumOutlineDescription" => {
573 let (sprite_sheet, sprite, size) = read_image(r)?;
574 ElementKind::FrustumOutline {
575 sprite_sheet,
576 sprite,
577 size,
578 }
579 }
580 "AnimatedImageDescription" => {
581 let (sprite_sheet, sprite, size) = read_image(r)?;
582 r.block(|r, _| {
583 let frames = (0..r.count()?).map(|_| r.string()).collect::<Result<_>>()?;
584 Ok(ElementKind::AnimatedImage {
585 sprite_sheet,
586 sprite,
587 size,
588 frames,
589 rate: r.i32()?,
590 })
591 })?
592 }
593 "ButtonDescription" => ElementKind::Button(read_button(r)?),
594 "CheckDescription" => {
595 let button = read_button(r)?;
596 ElementKind::Check {
597 button,
598 check: r.block(|r, _| r.external_ref())?,
599 }
600 }
601 "ComboDescription" => {
602 let button = read_button(r)?;
603 r.block(|r, _| {
604 Ok(ElementKind::Combo {
605 button,
606 refs: [r.external_ref()?, r.external_ref()?, r.external_ref()?],
607 })
608 })?
609 }
610 "TextDescription" => ElementKind::Text(read_text(r)?),
611 "LabelDescription" => {
612 let text = read_text(r)?;
613 r.block(|r, _| {
614 let sprite_sheet = r.external_ref()?;
615 let sprite = r.string()?;
616 let text_alignment = r.u32()?;
617 let orientation = r.u32()?;
618 let [min_text_width, gap, w, h] = i32s(r)?;
619 Ok(ElementKind::Label(Label {
620 text,
621 sprite_sheet,
622 sprite,
623 text_alignment,
624 orientation,
625 min_text_width,
626 gap,
627 icon_size: [w, h],
628 }))
629 })?
630 }
631 "ClipRegionDescription" => r.block(|r, _| {
632 Ok(ElementKind::ClipRegion {
633 unknown: i32s(r)?,
634 content: r.external_ref()?,
635 })
636 })?,
637 "ContainerDescription" => r.block(|r, _| {
638 Ok(ElementKind::Container {
639 unknown: i32s(r)?,
640 refs: [r.external_ref()?, r.external_ref()?, r.external_ref()?],
641 unknown_bools: [r.bool()?, r.bool()?],
642 })
643 })?,
644 "EditDescription" => r.block(|r, _| {
645 Ok(ElementKind::Edit {
646 font: r.external_ref()?,
647 text: r.string()?,
648 max_length: r.i32()?,
649 unknown_bool: r.bool()?,
650 unknown_string: r.string()?,
651 })
652 })?,
653 "GraphDescription" => r.block(|r, _| {
654 Ok(ElementKind::Graph {
655 unknown: i32s(r)?,
656 unknown_u32: r.u32()?,
657 sprite_sheet: r.external_ref()?,
658 sprite: r.string()?,
659 })
660 })?,
661 "LayoutDescription" => r.block(|r, _| {
662 let items = (0..r.count()?)
663 .map(|_| {
664 Ok(LayoutItem {
665 element: r.external_ref()?,
666 strings: strings(r)?,
667 unknown_u32: [r.u32()?, r.u32()?],
668 unknown_i32: i32s(r)?,
669 flag: r.bool()?,
670 })
671 })
672 .collect::<Result<_>>()?;
673 Ok(ElementKind::Layout { items })
674 })?,
675 "ListDescription" => r.block(|r, _| {
676 Ok(ElementKind::List {
677 refs: [r.external_ref()?, r.external_ref()?],
678 sprite_sheet: r.external_ref()?,
679 sprite: r.string()?,
680 unknown: [r.u32()?, r.u32()?],
681 })
682 })?,
683 "NumberEditDescription" => r.block(|r, _| {
684 Ok(ElementKind::NumberEdit {
685 font: r.external_ref()?,
686 unknown: i32s(r)?,
687 unknown_bool: r.bool()?,
688 })
689 })?,
690 "NumberDescription" => r.block(|r, _| {
691 Ok(ElementKind::Number {
692 font: r.external_ref()?,
693 color: r.u32()?,
694 unknown_int: r.i32()?,
695 unknown_float: r.f32()?,
696 unknown_bool: r.bool()?,
697 })
698 })?,
699 "SliderDescription" => r.block(|r, _| {
700 Ok(ElementKind::Slider {
701 track: r.external_ref()?,
702 sprite_sheet: r.external_ref()?,
703 sprites: strings(r)?,
704 unknown: i32s(r)?,
705 range: [r.f32()?, r.f32()?],
706 unknown_b0: r.i32()?,
707 })
708 })?,
709 "SpinnerDescription" => r.block(|r, _| {
710 Ok(ElementKind::Spinner {
711 refs: [r.external_ref()?, r.external_ref()?, r.external_ref()?],
712 unknown: r.i32()?,
713 })
714 })?,
715 "TabDescription" => r.block(|r, _| {
716 Ok(ElementKind::Tab {
717 refs: [r.external_ref()?, r.external_ref()?, r.external_ref()?],
718 sprite_sheet: r.external_ref()?,
719 sprites: strings(r)?,
720 unknown: r.i32()?,
721 tabs: (0..r.count()?)
722 .map(|_| Ok((r.external_ref()?, r.external_ref()?)))
723 .collect::<Result<_>>()?,
724 })
725 })?,
726 "ProgressDescription" => r.block(|r, _| {
727 Ok(ElementKind::Progress {
728 color: r.u32()?,
729 sprite_sheet: r.external_ref()?,
730 sprites: strings(r)?,
731 unknown_ref: r.external_ref()?,
732 })
733 })?,
734 "ScrollDescription" => r.block(|r, _| {
735 Ok(ElementKind::Scroll {
736 sprite_sheet: r.external_ref()?,
737 sprites: strings(r)?,
738 vertical: r.bool()?,
739 unknown: r.i32()?,
740 unknown_u32: [r.u32()?, r.u32()?],
741 })
742 })?,
743 "TableDescription" => r.block(|r, _| {
744 let unknown = i32s(r)?;
745 let cells = (0..r.count()?)
746 .map(|_| {
747 Ok(TableCell {
748 column: r.i32()?,
749 row: r.i32()?,
750 element: r.external_ref()?,
751 })
752 })
753 .collect::<Result<_>>()?;
754 Ok(ElementKind::Table { unknown, cells })
755 })?,
756 "DocumentTextDescription" => r.block(|r, _| {
757 Ok(ElementKind::DocumentText(DocumentText {
758 string_table: r.external_ref()?,
759 text: r.string()?,
760 sprite_sheet: r.external_ref()?,
761 bullets: (0..r.count()?).map(|_| r.string()).collect::<Result<_>>()?,
762 underline_sprite: r.string()?,
763 link_colors: [r.u32()?, r.u32()?, r.u32()?],
764 indent: r.i32()?,
765 width: r.i32()?,
766 fonts: refs(r)?,
767 sheets: refs(r)?,
768 colors: (0..r.count()?).map(|_| r.u32()).collect::<Result<_>>()?,
769 images: r.external_ref()?,
770 }))
771 })?,
772 _ => return Err(Error::Unsupported(format!("element class {class}"))),
773 };
774 Ok(Element { class, base, kind })
775}
776
777pub const ELEMENT_CLASSES: &[&str] = &[
779 "GroupDescription",
780 "PageDescription",
781 "RadioDescription",
782 "RibbonDescription",
783 "BorderDescription",
784 "ImageDescription",
785 "AnimatedImageDescription",
786 "FrustumOutlineDescription",
787 "ButtonDescription",
788 "CheckDescription",
789 "ComboDescription",
790 "TextDescription",
791 "LabelDescription",
792 "ClipRegionDescription",
793 "ContainerDescription",
794 "EditDescription",
795 "GraphDescription",
796 "LayoutDescription",
797 "ListDescription",
798 "NumberEditDescription",
799 "NumberDescription",
800 "SliderDescription",
801 "SpinnerDescription",
802 "TabDescription",
803 "ProgressDescription",
804 "ScrollDescription",
805 "TableDescription",
806 "DocumentTextDescription",
807];
808
809pub fn element(data: &[u8]) -> Result<Element> {
811 let (_, element) = crs::parse(data, |r, h| {
812 let class = ELEMENT_CLASSES
813 .iter()
814 .find(|c| type_id(c) == h.type_id)
815 .ok_or_else(|| {
816 Error::Format(format!("type id {:#010x} is not an element", h.type_id))
817 })?;
818 read_element(class, r)
819 })?;
820 Ok(element)
821}
822
823pub fn is_element(type_id_value: u32) -> bool {
825 ELEMENT_CLASSES.iter().any(|c| type_id(c) == type_id_value)
826}
827
828#[derive(Debug, Clone)]
832pub struct DialogBinding {
833 pub unknown: u32,
835 pub source: Ref,
837 pub target: Ref,
839 pub name: String,
841}
842
843#[derive(Debug, Clone)]
845pub struct Dialog {
846 pub unknown_40: u32,
848 pub root: Ref,
850 pub background_sheet: Ref,
852 pub background_sprite: String,
854 pub unknown_30: u32,
856 pub named: Vec<(Ref, String)>,
858 pub children: Vec<Ref>,
860 pub bindings: Vec<DialogBinding>,
862 pub dialogs: Vec<Ref>,
864 pub unknown_34: i32,
866}
867
868pub fn dialog(data: &[u8]) -> Result<Dialog> {
870 expect(data, "Dialog", |r| {
871 r.block(|r, _| {
872 let unknown_40 = r.u32()?;
873 let root = r.external_ref()?;
874 let background_sheet = r.external_ref()?;
875 let background_sprite = r.string()?;
876 let unknown_30 = r.u32()?;
877 let named = (0..r.count()?)
878 .map(|_| Ok((r.external_ref()?, r.string()?)))
879 .collect::<Result<_>>()?;
880 let children = refs(r)?;
881 let bindings = (0..r.count()?)
882 .map(|_| {
883 Ok(DialogBinding {
884 unknown: r.u32()?,
885 source: r.external_ref()?,
886 target: r.external_ref()?,
887 name: r.string()?,
888 })
889 })
890 .collect::<Result<_>>()?;
891 let dialogs = refs(r)?;
892 Ok(Dialog {
893 unknown_40,
894 root,
895 background_sheet,
896 background_sprite,
897 unknown_30,
898 named,
899 children,
900 bindings,
901 dialogs,
902 unknown_34: r.i32()?,
903 })
904 })
905 })
906}
907
908#[derive(Debug, Clone)]
912pub struct Sprite {
913 pub name: String,
915 pub source: String,
917 pub offset: [i32; 2],
919 pub width: i32,
921 pub height: i32,
923 pub unknown: i32,
925 pub uv: [f32; 4],
927}
928
929#[derive(Debug, Clone)]
931pub struct SpriteSheet {
932 pub material_path: String,
934 pub source_image: String,
936 pub width: i32,
938 pub height: i32,
940 pub unknown_flag: bool,
942 pub sprites: Vec<Sprite>,
944 pub material: Ref,
946}
947
948impl SpriteSheet {
949 pub fn sprite(&self, name: &str) -> Option<&Sprite> {
951 self.sprites.iter().find(|s| s.name == name)
952 }
953}
954
955pub fn sprite_sheet(data: &[u8]) -> Result<SpriteSheet> {
957 expect(data, "SpriteSheet", |r| {
958 r.block(|r, _| {
959 let material_path = r.string()?;
960 let source_image = r.string()?;
961 let width = r.i32()?;
962 let height = r.i32()?;
963 let unknown_flag = r.bool()?;
964 let sprites = (0..r.count()?)
965 .map(|_| {
966 let name = r.string()?;
967 let source = r.string()?;
968 let offset = i32s(r)?;
969 let width = r.i32()?;
970 let height = r.i32()?;
971 let unknown = r.i32()?;
972 Ok(Sprite {
973 name,
974 source,
975 offset,
976 width,
977 height,
978 unknown,
979 uv: r.f32s()?,
980 })
981 })
982 .collect::<Result<_>>()?;
983 Ok(SpriteSheet {
984 material_path,
985 source_image,
986 width,
987 height,
988 unknown_flag,
989 sprites,
990 material: r.external_ref()?,
991 })
992 })
993 })
994}
995
996#[derive(Debug, Clone)]
1000pub struct Cursor {
1001 pub sprite_sheet: Ref,
1003 pub hotspot: [i32; 2],
1005}
1006
1007pub fn cursor(data: &[u8]) -> Result<Cursor> {
1009 expect(data, "Cursor", |r| {
1010 r.block(|r, _| {
1011 Ok(Cursor {
1012 sprite_sheet: r.external_ref()?,
1013 hotspot: [r.i32()?, r.i32()?],
1014 })
1015 })
1016 })
1017}
1018
1019#[derive(Debug, Clone)]
1023pub struct Font {
1024 pub name: String,
1026 pub size: i32,
1028 pub sheet_width: i32,
1030 pub sheet_height: i32,
1032 pub character_set: Ref,
1034 pub sprite_sheet: Ref,
1036 pub material: Ref,
1038 pub char_map: Vec<i32>,
1040 pub glyphs: Vec<[i32; 4]>,
1043 pub unknown: [i32; 3],
1046}
1047
1048impl Font {
1049 pub fn baseline_offset(&self) -> i32 {
1053 self.size - self.unknown[0]
1054 }
1055
1056 pub fn glyph_index(&self, ch: char) -> Option<usize> {
1058 let g = *self.char_map.get(ch as usize)?;
1059 (g >= 0 && g != 0xFFFF && (g as usize) < self.glyphs.len()).then_some(g as usize)
1060 }
1061}
1062
1063pub fn font(data: &[u8]) -> Result<Font> {
1065 expect(data, "Font", |r| {
1066 r.block(|r, version| {
1067 if version < 2 {
1068 return Err(Error::Unsupported("Font block version < 2".into()));
1069 }
1070 let name = r.string()?;
1071 let size = r.i32()?;
1072 let _sheet_path = r.string()?;
1073 let sheet_width = r.i32()?;
1074 let sheet_height = r.i32()?;
1075 let _material_path = r.string()?;
1076 let _image_path = r.string()?;
1077 let character_set = r.external_ref()?;
1078 let sprite_sheet = r.external_ref()?;
1079 let material = r.external_ref()?;
1080 let char_map = (0..r.count()?).map(|_| r.i32()).collect::<Result<_>>()?;
1081 let glyphs = (0..r.count()?).map(|_| i32s(r)).collect::<Result<_>>()?;
1082 Ok(Font {
1083 name,
1084 size,
1085 sheet_width,
1086 sheet_height,
1087 character_set,
1088 sprite_sheet,
1089 material,
1090 char_map,
1091 glyphs,
1092 unknown: i32s(r)?,
1093 })
1094 })
1095 })
1096}
1097
1098pub fn string_table(data: &[u8]) -> Result<Vec<(String, String)>> {
1102 expect(data, "StringTable", |r| {
1103 r.block(|r, _| {
1104 (0..r.count()?)
1105 .map(|_| Ok((r.string()?, r.string()?)))
1106 .collect()
1107 })
1108 })
1109}
1110
1111fn expect<T>(data: &[u8], class: &str, read: impl FnOnce(&mut Reader) -> Result<T>) -> Result<T> {
1112 let (_, value) = crs::parse(data, |r, h| {
1113 if h.type_id != type_id(class) {
1114 return format_err(format!(
1115 "expected {class}, found type id {:#010x}",
1116 h.type_id
1117 ));
1118 }
1119 read(r)
1120 })?;
1121 Ok(value)
1122}