Skip to main content

rgpre/import/rob/
parser.rs

1use super::{RobFile, RobHeader, RobSegment, SegmentBBox};
2use log::warn;
3use nom::{
4    IResult, Parser,
5    bytes::complete::{tag, take},
6    combinator::opt,
7    number::complete::{be_u32, le_u16, le_u32},
8};
9
10/// Parses the fixed ROB file header.
11pub fn parse_rob_header(input: &[u8]) -> IResult<&[u8], RobHeader> {
12    let (input, found_archive_magic) = opt(tag("OARC")).parse(input)?;
13    if found_archive_magic.is_none() {
14        warn!("OARC header not found where expected");
15    }
16    let (input, unknown_04) = be_u32(input)?;
17    let (input, num_segments) = le_u32(input)?;
18    let (input, found_segment_magic) = opt(tag("OARD")).parse(input)?;
19    if found_segment_magic.is_none() {
20        warn!("OARD header not found where expected");
21    }
22    let (input, payload_size) = be_u32(input)?;
23
24    Ok((
25        input,
26        RobHeader {
27            unknown_04,
28            num_segments,
29            payload_size,
30        },
31    ))
32}
33
34/// Parses a ROB segment header without consuming its model payload.
35pub fn parse_rob_segment_header(input: &[u8]) -> IResult<&[u8], RobSegment> {
36    let (input, total_size) = le_u32(input)?;
37    let (input, segment_name) = take(8u8)(input)?;
38    let (input, segment_type) = le_u16(input)?;
39    let (input, segment_flags) = le_u16(input)?;
40
41    let (input, segment_attribs) = nom::number::complete::le_u8(input)?;
42    let (input, face_count_low) = take(3usize)(input)?;
43    let (input, unused_14) = be_u32(input)?;
44    let (input, _reserved_18) = le_u32(input)?;
45
46    let (input, extent_x) = le_u32(input)?;
47    let (input, extent_y) = le_u32(input)?;
48    let (input, extent_z) = le_u32(input)?;
49
50    let (input, _reserved_28) = le_u32(input)?;
51    let (input, _reserved_2c) = le_u32(input)?;
52    let (input, _reserved_30) = le_u32(input)?;
53
54    let (input, positive_x) = le_u32(input)?;
55    let (input, positive_y) = le_u32(input)?;
56    let (input, positive_z) = le_u32(input)?;
57
58    let (input, negative_x) = le_u32(input)?;
59    let (input, negative_y) = le_u32(input)?;
60    let (input, negative_z) = le_u32(input)?;
61
62    let (input, data_size) = le_u32(input)?;
63
64    let mut segment_name_bytes = [0_u8; 8];
65    segment_name_bytes.copy_from_slice(segment_name);
66    let mut face_count_low_bytes = [0_u8; 3];
67    face_count_low_bytes.copy_from_slice(face_count_low);
68
69    Ok((
70        input,
71        RobSegment {
72            total_size,
73            segment_name: segment_name_bytes,
74            segment_type,
75            segment_flags,
76            segment_attribs,
77            _face_count_low: face_count_low_bytes,
78            _unused_14: unused_14,
79            bbox: SegmentBBox {
80                extent_x,
81                extent_y,
82                extent_z,
83                positive_x,
84                positive_y,
85                positive_z,
86                negative_x,
87                negative_y,
88                negative_z,
89            },
90            data_size,
91            data: Vec::new(),
92        },
93    ))
94}
95
96/// Parses one ROB segment and its optional model payload.
97pub fn parse_rob_segment(input: &[u8]) -> IResult<&[u8], RobSegment> {
98    let (input, mut segment) = parse_rob_segment_header(input)?;
99    let (input, data) = if segment.data_size > 0 && !segment.points_to_external_file() {
100        take(usize::try_from(segment.data_size).unwrap_or(usize::MAX))(input)?
101    } else {
102        (input, &[] as &[u8])
103    };
104    segment.data = data.to_vec();
105    Ok((input, segment))
106}
107
108/// Parses a complete ROB file into header and segment records.
109pub fn parse_rob_file(input: &[u8]) -> IResult<&[u8], RobFile> {
110    let file_size = u32::try_from(input.len()).unwrap_or(u32::MAX);
111    let (input, header) = parse_rob_header(input)?;
112
113    // Validated across all 72 shipped ROB files: payload_size == file_size - 24
114    // (24 = 20-byte header + 4-byte "END " footer)
115    let expected_payload = file_size.saturating_sub(24);
116    if header.payload_size != expected_payload {
117        warn!(
118            "ROB payload_size {} does not match expected {} (file_size {} - 24)",
119            header.payload_size, expected_payload, file_size
120        );
121    }
122
123    let mut segments = Vec::new();
124    let mut remaining_input = input;
125
126    for _ in 0..header.num_segments {
127        let (input, segment) = parse_rob_segment(remaining_input)?;
128        segments.push(segment);
129        remaining_input = input;
130    }
131
132    let (remaining_input, end_marker) = opt(tag("END ")).parse(remaining_input)?;
133    if end_marker.is_none() {
134        warn!("END marker not found at end of file");
135    }
136
137    Ok((remaining_input, RobFile { header, segments }))
138}