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