1use std::io::Read;
4
5use crate::binary;
6
7use super::layout::{self, RimHeader};
8use super::{Rim, RimBinaryError, RimReadOptions, RimResource};
9
10#[cfg_attr(
21 feature = "tracing",
22 tracing::instrument(level = "debug", skip(reader))
23)]
24pub fn read_rim<R: Read>(reader: &mut R) -> Result<Rim, RimBinaryError> {
25 read_rim_with_options(reader, RimReadOptions::default())
26}
27
28#[cfg_attr(
35 feature = "tracing",
36 tracing::instrument(level = "debug", skip(reader))
37)]
38pub fn read_rim_with_options<R: Read>(
39 reader: &mut R,
40 options: RimReadOptions,
41) -> Result<Rim, RimBinaryError> {
42 let mut bytes = Vec::new();
43 reader.read_to_end(&mut bytes)?;
44 crate::trace_debug!(bytes_len = bytes.len(), "read rim bytes from reader");
45 read_rim_from_bytes_with_options(&bytes, options)
46}
47
48#[cfg_attr(
55 feature = "tracing",
56 tracing::instrument(level = "debug", skip(bytes), fields(bytes_len = bytes.len()))
57)]
58pub fn read_rim_from_bytes(bytes: &[u8]) -> Result<Rim, RimBinaryError> {
59 read_rim_from_bytes_with_options(bytes, RimReadOptions::default())
60}
61
62#[cfg_attr(
84 feature = "tracing",
85 tracing::instrument(
86 level = "debug",
87 skip(bytes),
88 fields(bytes_len = bytes.len(), input_mode = ?options.input)
89 )
90)]
91pub fn read_rim_from_bytes_with_options(
92 bytes: &[u8],
93 options: RimReadOptions,
94) -> Result<Rim, RimBinaryError> {
95 let header = RimHeader::parse(bytes, options)?;
96 let keys_table_size = header.keys_table_size()?;
97 binary::check_slice_in_bounds(bytes, header.keys_offset, keys_table_size, "keys table")?;
98 let keys_table = bytes
99 .get(header.keys_offset..header.keys_offset + keys_table_size)
100 .ok_or_else(|| RimBinaryError::InvalidData("keys table is missing".into()))?;
101
102 let mut resources = Vec::with_capacity(header.entry_count);
103 for key_index in 0..header.entry_count {
104 let key = layout::parse_key_entry(keys_table, key_index)?;
105 binary::check_slice_in_bounds(
106 bytes,
107 key.data_offset,
108 key.data_size,
109 &format!("resource data[{key_index}]"),
110 )?;
111 let data = bytes
112 .get(key.data_offset..(key.data_offset + key.data_size))
113 .ok_or_else(|| {
114 RimBinaryError::InvalidData(format!(
115 "resource data slice missing for keys[{key_index}]"
116 ))
117 })?
118 .to_vec();
119 resources.push(RimResource {
120 resref: key.resref,
121 resource_type: key.resource_type,
122 data,
123 });
124 }
125
126 let rim = Rim {
127 reserved_0x08: header.reserved_0x08,
128 reserved_0x14: header.reserved_0x14,
129 reserved_0x18: header.reserved_0x18,
130 resources,
131 };
132 crate::trace_debug!(
133 resource_count = rim.resources.len(),
134 "parsed rim from bytes"
135 );
136 Ok(rim)
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142 use crate::rim::{write_rim_to_vec, RimReadMode, FILE_HEADER_SIZE, RIM_MAGIC, RIM_VERSION_V10};
143 use rakata_core::ResRef;
144
145 const TEST_RIM: &[u8] = include_bytes!(concat!(
146 env!("CARGO_MANIFEST_DIR"),
147 "/../../fixtures/test.rim"
148 ));
149 const CAPSULE_RIM: &[u8] = include_bytes!(concat!(
150 env!("CARGO_MANIFEST_DIR"),
151 "/../../fixtures/capsule.rim"
152 ));
153 const CORRUPTED_RIM: &[u8] = include_bytes!(concat!(
154 env!("CARGO_MANIFEST_DIR"),
155 "/../../fixtures/test_corrupted.rim"
156 ));
157
158 #[test]
159 fn parses_rim_fixture() {
160 let rim = read_rim_from_bytes(TEST_RIM).expect("fixture should parse");
161 assert_eq!(rim.resources.len(), 3);
162 assert_eq!(
163 rim.resource(
164 &ResRef::new("1").unwrap(),
165 rakata_core::ResourceTypeCode::from_raw_id(10)
166 ),
167 Some(b"abc".as_slice())
168 );
169 assert_eq!(
170 rim.resource(
171 &ResRef::new("2").unwrap(),
172 rakata_core::ResourceTypeCode::from_raw_id(10)
173 ),
174 Some(b"def".as_slice())
175 );
176 assert_eq!(
177 rim.resource(
178 &ResRef::new("3").unwrap(),
179 rakata_core::ResourceTypeCode::from_raw_id(10)
180 ),
181 Some(b"ghi".as_slice())
182 );
183 }
184
185 #[test]
186 fn parses_capsule_rim_fixture() {
187 let rim = read_rim_from_bytes(CAPSULE_RIM).expect("fixture should parse");
188 assert_eq!(rim.resources.len(), 3);
189 assert_eq!(rim.resources[0].resref, "m13aa");
190 assert!(!rim.resources[0].data.is_empty());
191 }
192
193 #[test]
194 fn roundtrip_synthetic_rim() {
195 let mut rim = Rim::new();
196 rim.push_resource(
197 ResRef::new("alpha").unwrap(),
198 rakata_core::ResourceTypeCode::from_raw_id(2017),
199 b"abc".to_vec(),
200 );
201 rim.push_resource(
202 ResRef::new("beta").unwrap(),
203 rakata_core::ResourceTypeCode::from_raw_id(2018),
204 b"defghi".to_vec(),
205 );
206
207 let bytes = write_rim_to_vec(&rim).expect("write should succeed");
208 let parsed = read_rim_from_bytes(&bytes).expect("read should succeed");
209 assert_eq!(parsed, rim);
210 }
211
212 #[test]
213 fn roundtrip_preserves_unknown_resource_type_ids() {
214 let mut rim = Rim::new();
215 rim.push_resource(
216 ResRef::new("mystery").unwrap(),
217 rakata_core::ResourceTypeCode::from_raw_id(42424),
218 vec![1, 2, 3],
219 );
220
221 let bytes = write_rim_to_vec(&rim).expect("write should succeed");
222 let parsed = read_rim_from_bytes(&bytes).expect("read should succeed");
223 assert_eq!(parsed.resources.len(), 1);
224 assert_eq!(parsed.resources[0].resource_type.raw_id(), 42424);
225 assert_eq!(parsed.resources[0].resource_type.known_type(), None);
226 }
227
228 #[test]
229 fn read_write_roundtrip_preserves_fixture_semantics() {
230 let parsed = read_rim_from_bytes(TEST_RIM).expect("fixture should parse");
231 let bytes = write_rim_to_vec(&parsed).expect("write should succeed");
232 let reparsed = read_rim_from_bytes(&bytes).expect("re-read should succeed");
233 assert_eq!(reparsed, parsed);
234 }
235
236 #[test]
237 fn writer_is_deterministic_for_parsed_fixture() {
238 let parsed = read_rim_from_bytes(TEST_RIM).expect("fixture should parse");
239 let first = write_rim_to_vec(&parsed).expect("first write should succeed");
240 let second = write_rim_to_vec(&parsed).expect("second write should succeed");
241 assert_eq!(first, second, "canonical RIM writer output drifted");
242 }
243
244 #[test]
245 fn rejects_invalid_magic() {
246 let mut bytes = vec![0_u8; FILE_HEADER_SIZE];
247 bytes[0..4].copy_from_slice(b"NOPE");
248 bytes[4..8].copy_from_slice(&RIM_VERSION_V10);
249 let err = read_rim_from_bytes(&bytes).expect_err("must fail");
250 assert!(matches!(err, RimBinaryError::InvalidMagic(_)));
251 }
252
253 #[test]
254 fn rejects_invalid_version() {
255 let mut bytes = vec![0_u8; FILE_HEADER_SIZE];
256 bytes[0..4].copy_from_slice(&RIM_MAGIC);
257 bytes[4..8].copy_from_slice(b"V9.9");
258 let err = read_rim_from_bytes(&bytes).expect_err("must fail");
259 assert!(matches!(err, RimBinaryError::InvalidVersion(_)));
260 }
261
262 #[test]
263 fn rejects_truncated_header() {
264 let bytes = vec![0_u8; FILE_HEADER_SIZE - 1];
265 let err = read_rim_from_bytes(&bytes).expect_err("must fail");
266 assert!(matches!(err, RimBinaryError::InvalidHeader(_)));
267 }
268
269 #[test]
270 fn canonical_mode_falls_back_when_keys_offset_is_zero() {
271 let mut bytes = TEST_RIM.to_vec();
272 bytes[16..20].copy_from_slice(&0_u32.to_le_bytes());
273
274 let rim = read_rim_from_bytes_with_options(
275 &bytes,
276 RimReadOptions {
277 input: RimReadMode::CanonicalK1,
278 },
279 )
280 .expect("canonical mode should parse with fallback offsets");
281 assert_eq!(rim.resources.len(), 3);
282 assert_eq!(
283 rim.resource(
284 &ResRef::new("1").unwrap(),
285 rakata_core::ResourceTypeCode::from_raw_id(10)
286 ),
287 Some(b"abc".as_slice())
288 );
289 }
290
291 #[test]
292 fn strict_mode_rejects_zero_keys_offset() {
293 let mut bytes = TEST_RIM.to_vec();
294 bytes[16..20].copy_from_slice(&0_u32.to_le_bytes());
295
296 let err = read_rim_from_bytes_with_options(
297 &bytes,
298 RimReadOptions {
299 input: RimReadMode::StrictExplicitOffsets,
300 },
301 )
302 .expect_err("strict mode should reject");
303 assert!(matches!(err, RimBinaryError::InvalidHeader(_)));
304 }
305
306 #[test]
307 fn rejects_malformed_fixture_with_invalid_key_offset() {
308 let err = read_rim_from_bytes(CORRUPTED_RIM).expect_err("must fail");
309 assert!(matches!(
310 err,
311 RimBinaryError::InvalidHeader(_) | RimBinaryError::InvalidData(_)
312 ));
313 }
314
315 #[test]
316 fn resref_validation_rejects_long_names() {
317 let result = ResRef::new("this_name_is_too_long");
319 assert!(result.is_err());
320 }
321
322 #[test]
323 fn reserved_fields_survive_roundtrip() {
324 let mut rim = Rim::new();
325 rim.reserved_0x08 = 0xDEADBEEF;
326 rim.reserved_0x14 = 0x12345678;
327 rim.reserved_0x18[0] = 0xAB;
328 rim.reserved_0x18[47] = 0xCD;
329 rim.reserved_0x18[95] = 0xEF;
330 rim.push_resource(
331 ResRef::new("a").unwrap(),
332 rakata_core::ResourceTypeCode::from_raw_id(10),
333 b"test".to_vec(),
334 );
335
336 let bytes = write_rim_to_vec(&rim).expect("write should succeed");
337 let parsed = read_rim_from_bytes(&bytes).expect("read should succeed");
338 assert_eq!(parsed.reserved_0x08, 0xDEADBEEF);
339 assert_eq!(parsed.reserved_0x14, 0x12345678);
340 assert_eq!(parsed.reserved_0x18[0], 0xAB);
341 assert_eq!(parsed.reserved_0x18[47], 0xCD);
342 assert_eq!(parsed.reserved_0x18[95], 0xEF);
343 }
344}