1use std::io::Read;
4
5use rakata_core::{decode_text_strict, text_encoding_for_language, ResRef, StrRef};
6
7use super::{
8 binary, to_usize, FieldType, Gff, GffBinaryError, GffField, GffLocalizedString,
9 GffLocalizedSubstring, GffStruct, GffValue, DEFAULT_TEXT_ENCODING, FIELD_ENTRY_SIZE,
10 GFF_HEADER_SIZE, GFF_VERSION_V32, LABEL_SIZE, STRUCT_ENTRY_SIZE,
11};
12
13#[cfg_attr(
22 feature = "tracing",
23 tracing::instrument(level = "debug", skip(reader))
24)]
25pub fn read_gff<R: Read>(reader: &mut R) -> Result<Gff, GffBinaryError> {
26 let mut bytes = Vec::new();
27 reader.read_to_end(&mut bytes)?;
28 crate::trace_debug!(bytes_len = bytes.len(), "read gff bytes from reader");
29 read_gff_from_bytes(&bytes)
30}
31
32#[cfg_attr(
54 feature = "tracing",
55 tracing::instrument(level = "debug", skip(bytes), fields(bytes_len = bytes.len()))
56)]
57pub fn read_gff_from_bytes(bytes: &[u8]) -> Result<Gff, GffBinaryError> {
58 let header = parse_header(bytes)?;
59 let labels = read_labels(bytes, &header)?;
60 let parser = GffParser {
61 bytes,
62 header,
63 labels,
64 };
65 let root = parser.read_struct(0, 0)?;
66 crate::trace_debug!(
67 file_type = ?parser.header.file_type,
68 struct_count = parser.header.struct_count,
69 field_count = parser.header.field_count,
70 label_count = parser.header.label_count,
71 root_field_count = root.fields.len(),
72 "parsed gff from bytes"
73 );
74 Ok(Gff {
75 file_type: parser.header.file_type,
76 root,
77 })
78}
79
80#[derive(Debug, Clone, Copy)]
81struct GffHeader {
82 file_type: [u8; 4],
83 struct_offset: usize,
84 struct_count: usize,
85 field_offset: usize,
86 field_count: usize,
87 label_offset: usize,
88 label_count: usize,
89 field_data_offset: usize,
90 field_data_count: usize,
91 field_indices_offset: usize,
92 field_indices_count: usize,
93 list_indices_offset: usize,
94 list_indices_count: usize,
95}
96
97struct GffParser<'a> {
98 bytes: &'a [u8],
99 header: GffHeader,
100 labels: Vec<super::GffLabel>,
101}
102
103impl<'a> GffParser<'a> {
104 fn read_struct(&self, struct_index: usize, depth: usize) -> Result<GffStruct, GffBinaryError> {
105 let depth_limit = self.header.struct_count.saturating_mul(2);
111 if depth > depth_limit {
112 return Err(GffBinaryError::InvalidData(
113 "detected nested struct cycle while reading".into(),
114 ));
115 }
116 if struct_index >= self.header.struct_count {
117 return Err(GffBinaryError::InvalidData(format!(
118 "struct index {struct_index} out of range"
119 )));
120 }
121
122 let base = self
123 .header
124 .struct_offset
125 .checked_add(
126 struct_index
127 .checked_mul(STRUCT_ENTRY_SIZE)
128 .ok_or_else(|| GffBinaryError::InvalidHeader("struct index overflow".into()))?,
129 )
130 .ok_or_else(|| GffBinaryError::InvalidHeader("struct base overflow".into()))?;
131 let struct_id = read_i32(self.bytes, base)?;
132 let data_or_offset = binary::read_u32(self.bytes, base + 4)?;
133 let field_count = usize::try_from(binary::read_u32(self.bytes, base + 8)?)
134 .map_err(|_| GffBinaryError::InvalidData("field count does not fit usize".into()))?;
135
136 let mut fields = Vec::with_capacity(field_count);
137 match field_count {
138 0 => {}
139 1 => {
140 let field_index = usize::try_from(data_or_offset).map_err(|_| {
141 GffBinaryError::InvalidData("single field index does not fit usize".into())
142 })?;
143 fields.push(self.read_field(field_index, depth + 1)?);
144 }
145 _ => {
146 let indices_rel_offset = usize::try_from(data_or_offset).map_err(|_| {
147 GffBinaryError::InvalidData("field indices offset does not fit usize".into())
148 })?;
149 let indices_size = field_count.checked_mul(4).ok_or_else(|| {
150 GffBinaryError::InvalidData("field indices size overflow".into())
151 })?;
152 if indices_rel_offset
153 .checked_add(indices_size)
154 .is_none_or(|end| end > self.header.field_indices_count)
155 {
156 return Err(GffBinaryError::InvalidData(format!(
157 "field indices block out of range for struct {struct_index}"
158 )));
159 }
160 let indices_base = self
161 .header
162 .field_indices_offset
163 .checked_add(indices_rel_offset)
164 .ok_or_else(|| {
165 GffBinaryError::InvalidData("field indices absolute offset overflow".into())
166 })?;
167 for i in 0..field_count {
168 let field_index =
169 usize::try_from(binary::read_u32(self.bytes, indices_base + i * 4)?)
170 .map_err(|_| {
171 GffBinaryError::InvalidData(
172 "field index from field-indices table does not fit usize"
173 .into(),
174 )
175 })?;
176 fields.push(self.read_field(field_index, depth + 1)?);
177 }
178 }
179 }
180
181 Ok(GffStruct { struct_id, fields })
182 }
183
184 fn read_field(&self, field_index: usize, depth: usize) -> Result<GffField, GffBinaryError> {
185 if field_index >= self.header.field_count {
186 return Err(GffBinaryError::InvalidData(format!(
187 "field index {field_index} out of range"
188 )));
189 }
190 let base = self
191 .header
192 .field_offset
193 .checked_add(
194 field_index
195 .checked_mul(FIELD_ENTRY_SIZE)
196 .ok_or_else(|| GffBinaryError::InvalidHeader("field index overflow".into()))?,
197 )
198 .ok_or_else(|| GffBinaryError::InvalidHeader("field base overflow".into()))?;
199
200 let field_type_raw = binary::read_u32(self.bytes, base)?;
201 let field_type = FieldType::try_from(field_type_raw)
202 .map_err(|_| GffBinaryError::InvalidFieldType(field_type_raw))?;
203 let label_index = usize::try_from(binary::read_u32(self.bytes, base + 4)?)
204 .map_err(|_| GffBinaryError::InvalidData("label index does not fit usize".into()))?;
205 let data_or_offset = binary::read_u32(self.bytes, base + 8)?;
206 let label = *self.labels.get(label_index).ok_or_else(|| {
207 GffBinaryError::InvalidData(format!("label index {label_index} out of range"))
208 })?;
209
210 let value = match field_type {
211 FieldType::UInt8 => {
212 GffValue::UInt8(u8::try_from(data_or_offset & 0xFF).expect("masked to 8 bits"))
213 }
214 FieldType::Int8 => {
215 GffValue::Int8(i8::from_le_bytes([
216 u8::try_from(data_or_offset & 0xFF).expect("masked to 8 bits")
217 ]))
218 }
219 FieldType::UInt16 => {
220 GffValue::UInt16(u16::try_from(data_or_offset & 0xFFFF).expect("masked to 16 bits"))
221 }
222 FieldType::Int16 => GffValue::Int16(i16::from_le_bytes(
223 u16::try_from(data_or_offset & 0xFFFF)
224 .expect("masked to 16 bits")
225 .to_le_bytes(),
226 )),
227 FieldType::UInt32 => GffValue::UInt32(data_or_offset),
228 FieldType::Int32 => GffValue::Int32(i32::from_le_bytes(data_or_offset.to_le_bytes())),
229 FieldType::Single => GffValue::Single(f32::from_bits(data_or_offset)),
230 FieldType::UInt64 => GffValue::UInt64(read_u64_at_field_data(self, data_or_offset)?),
231 FieldType::Int64 => GffValue::Int64(i64::from_le_bytes(
232 read_u64_at_field_data(self, data_or_offset)?.to_le_bytes(),
233 )),
234 FieldType::Double => GffValue::Double(f64::from_bits(read_u64_at_field_data(
235 self,
236 data_or_offset,
237 )?)),
238 FieldType::String => GffValue::String(read_sized_string(
239 self,
240 data_or_offset,
241 format!("field[{field_index}] string"),
242 )?),
243 FieldType::ResRef => {
244 let raw = read_sized_string_u8(
245 self,
246 data_or_offset,
247 format!("field[{field_index}] resref"),
248 )?;
249 let resref = ResRef::new(&raw).map_err(|err| {
250 GffBinaryError::InvalidData(format!(
251 "field[{field_index}] resref `{raw}`: {err}"
252 ))
253 })?;
254 GffValue::ResRef(resref)
255 }
256 FieldType::LocalizedString => {
257 GffValue::LocalizedString(read_localized_string(self, data_or_offset, field_index)?)
258 }
259 FieldType::Binary => GffValue::Binary(read_binary_blob(self, data_or_offset)?),
260 FieldType::Struct => {
261 let struct_index = usize::try_from(data_or_offset).map_err(|_| {
262 GffBinaryError::InvalidData("nested struct index does not fit usize".into())
263 })?;
264 GffValue::Struct(Box::new(self.read_struct(struct_index, depth + 1)?))
265 }
266 FieldType::List => GffValue::List(read_struct_list(self, data_or_offset, depth + 1)?),
267 FieldType::Vector4 => GffValue::Vector4(read_vector4(self, data_or_offset)?),
268 FieldType::Vector3 => GffValue::Vector3(read_vector3(self, data_or_offset)?),
269 };
270
271 Ok(GffField { label, value })
272 }
273}
274
275fn parse_header(bytes: &[u8]) -> Result<GffHeader, GffBinaryError> {
276 if bytes.len() < GFF_HEADER_SIZE {
277 return Err(GffBinaryError::InvalidHeader(
278 "file smaller than GFF header".into(),
279 ));
280 }
281 let file_type = binary::read_fourcc(bytes, 0)?;
282 let version = binary::read_fourcc(bytes, 4)?;
283 binary::expect_fourcc(version, GFF_VERSION_V32).map_err(GffBinaryError::InvalidVersion)?;
284
285 let struct_offset = to_usize(binary::read_u32(bytes, 8)?, "struct_offset")?;
286 let struct_count = to_usize(binary::read_u32(bytes, 12)?, "struct_count")?;
287 let field_offset = to_usize(binary::read_u32(bytes, 16)?, "field_offset")?;
288 let field_count = to_usize(binary::read_u32(bytes, 20)?, "field_count")?;
289 let label_offset = to_usize(binary::read_u32(bytes, 24)?, "label_offset")?;
290 let label_count = to_usize(binary::read_u32(bytes, 28)?, "label_count")?;
291 let field_data_offset = to_usize(binary::read_u32(bytes, 32)?, "field_data_offset")?;
292 let field_data_count = to_usize(binary::read_u32(bytes, 36)?, "field_data_count")?;
293 let field_indices_offset = to_usize(binary::read_u32(bytes, 40)?, "field_indices_offset")?;
294 let field_indices_count = to_usize(binary::read_u32(bytes, 44)?, "field_indices_count")?;
295 let list_indices_offset = to_usize(binary::read_u32(bytes, 48)?, "list_indices_offset")?;
296 let list_indices_count = to_usize(binary::read_u32(bytes, 52)?, "list_indices_count")?;
297
298 check_table_bounds(
299 bytes.len(),
300 struct_offset,
301 struct_count
302 .checked_mul(STRUCT_ENTRY_SIZE)
303 .ok_or(GffBinaryError::InvalidHeader(
304 "struct table size overflow".into(),
305 ))?,
306 "struct table",
307 )?;
308 check_table_bounds(
309 bytes.len(),
310 field_offset,
311 field_count
312 .checked_mul(FIELD_ENTRY_SIZE)
313 .ok_or(GffBinaryError::InvalidHeader(
314 "field table size overflow".into(),
315 ))?,
316 "field table",
317 )?;
318 check_table_bounds(
319 bytes.len(),
320 label_offset,
321 label_count
322 .checked_mul(LABEL_SIZE)
323 .ok_or(GffBinaryError::InvalidHeader(
324 "label table size overflow".into(),
325 ))?,
326 "label table",
327 )?;
328 check_table_bounds(
329 bytes.len(),
330 field_data_offset,
331 field_data_count,
332 "field data",
333 )?;
334 check_table_bounds(
335 bytes.len(),
336 field_indices_offset,
337 field_indices_count,
338 "field indices",
339 )?;
340 check_table_bounds(
341 bytes.len(),
342 list_indices_offset,
343 list_indices_count,
344 "list indices",
345 )?;
346
347 if struct_count == 0 {
348 return Err(GffBinaryError::InvalidHeader(
349 "struct table is empty (missing root struct)".into(),
350 ));
351 }
352
353 Ok(GffHeader {
354 file_type,
355 struct_offset,
356 struct_count,
357 field_offset,
358 field_count,
359 label_offset,
360 label_count,
361 field_data_offset,
362 field_data_count,
363 field_indices_offset,
364 field_indices_count,
365 list_indices_offset,
366 list_indices_count,
367 })
368}
369
370fn read_labels(bytes: &[u8], header: &GffHeader) -> Result<Vec<super::GffLabel>, GffBinaryError> {
371 let mut labels = Vec::with_capacity(header.label_count);
372 for label_index in 0..header.label_count {
373 let offset = header
374 .label_offset
375 .checked_add(
376 label_index
377 .checked_mul(LABEL_SIZE)
378 .ok_or_else(|| GffBinaryError::InvalidHeader("label offset overflow".into()))?,
379 )
380 .ok_or_else(|| GffBinaryError::InvalidHeader("label base overflow".into()))?;
381 let raw = bytes
382 .get(offset..offset + LABEL_SIZE)
383 .ok_or_else(|| GffBinaryError::InvalidHeader("label slice out of range".into()))?;
384 let end = raw.iter().position(|byte| *byte == 0).unwrap_or(LABEL_SIZE);
385 let label = decode_text_strict(&raw[..end], DEFAULT_TEXT_ENCODING).map_err(|source| {
386 GffBinaryError::TextDecoding {
387 context: format!("label[{label_index}]"),
388 source,
389 }
390 })?;
391 let gff_label = super::GffLabel::new(&label).map_err(|err| {
392 GffBinaryError::InvalidData(format!("label[{label_index}] `{label}` is invalid: {err}"))
393 })?;
394 labels.push(gff_label);
395 }
396 Ok(labels)
397}
398
399fn read_u64_at_field_data(parser: &GffParser<'_>, offset: u32) -> Result<u64, GffBinaryError> {
400 let offset = to_usize(offset, "field_data_offset")?;
401 let absolute = parser
402 .header
403 .field_data_offset
404 .checked_add(offset)
405 .ok_or_else(|| GffBinaryError::InvalidData("field data absolute offset overflow".into()))?;
406 let end_rel = offset
407 .checked_add(8)
408 .ok_or_else(|| GffBinaryError::InvalidData("field data u64 end overflow".into()))?;
409 if end_rel > parser.header.field_data_count {
410 return Err(GffBinaryError::InvalidData(
411 "field data u64 read out of range".into(),
412 ));
413 }
414 Ok(binary::read_u64(parser.bytes, absolute)?)
415}
416
417fn read_sized_string(
418 parser: &GffParser<'_>,
419 offset: u32,
420 context: String,
421) -> Result<String, GffBinaryError> {
422 let offset = to_usize(offset, "field_data_offset")?;
423 let base = parser
424 .header
425 .field_data_offset
426 .checked_add(offset)
427 .ok_or_else(|| GffBinaryError::InvalidData("string base overflow".into()))?;
428 let len = to_usize(binary::read_u32(parser.bytes, base)?, "string_length")?;
429 let data_rel_end = offset
430 .checked_add(4)
431 .and_then(|v| v.checked_add(len))
432 .ok_or_else(|| GffBinaryError::InvalidData("string end overflow".into()))?;
433 if data_rel_end > parser.header.field_data_count {
434 return Err(GffBinaryError::InvalidData(
435 "string payload exceeds field data bounds".into(),
436 ));
437 }
438 let data_start = base + 4;
439 let raw = parser
440 .bytes
441 .get(data_start..data_start + len)
442 .ok_or_else(|| GffBinaryError::InvalidData("string bytes out of range".into()))?;
443 decode_text_strict(raw, DEFAULT_TEXT_ENCODING)
444 .map_err(|source| GffBinaryError::TextDecoding { context, source })
445}
446
447fn read_sized_string_u8(
448 parser: &GffParser<'_>,
449 offset: u32,
450 context: String,
451) -> Result<String, GffBinaryError> {
452 let offset = to_usize(offset, "field_data_offset")?;
453 let base = parser
454 .header
455 .field_data_offset
456 .checked_add(offset)
457 .ok_or_else(|| GffBinaryError::InvalidData("resref base overflow".into()))?;
458 let len =
459 usize::from(*parser.bytes.get(base).ok_or_else(|| {
460 GffBinaryError::InvalidData("resref length byte out of range".into())
461 })?);
462 let data_rel_end = offset
463 .checked_add(1)
464 .and_then(|v| v.checked_add(len))
465 .ok_or_else(|| GffBinaryError::InvalidData("resref end overflow".into()))?;
466 if data_rel_end > parser.header.field_data_count {
467 return Err(GffBinaryError::InvalidData(
468 "resref payload exceeds field data bounds".into(),
469 ));
470 }
471 let data_start = base + 1;
472 let raw = parser
473 .bytes
474 .get(data_start..data_start + len)
475 .ok_or_else(|| GffBinaryError::InvalidData("resref bytes out of range".into()))?;
476 decode_text_strict(raw, DEFAULT_TEXT_ENCODING)
477 .map_err(|source| GffBinaryError::TextDecoding { context, source })
478}
479
480fn read_binary_blob(parser: &GffParser<'_>, offset: u32) -> Result<Vec<u8>, GffBinaryError> {
481 let offset = to_usize(offset, "field_data_offset")?;
482 let base = parser
483 .header
484 .field_data_offset
485 .checked_add(offset)
486 .ok_or_else(|| GffBinaryError::InvalidData("binary base overflow".into()))?;
487 let len = to_usize(binary::read_u32(parser.bytes, base)?, "binary_length")?;
488 let data_rel_end = offset
489 .checked_add(4)
490 .and_then(|v| v.checked_add(len))
491 .ok_or_else(|| GffBinaryError::InvalidData("binary end overflow".into()))?;
492 if data_rel_end > parser.header.field_data_count {
493 return Err(GffBinaryError::InvalidData(
494 "binary payload exceeds field data bounds".into(),
495 ));
496 }
497 let data_start = base + 4;
498 let raw = parser
499 .bytes
500 .get(data_start..data_start + len)
501 .ok_or_else(|| GffBinaryError::InvalidData("binary bytes out of range".into()))?;
502 Ok(raw.to_vec())
503}
504
505fn read_vector4(parser: &GffParser<'_>, offset: u32) -> Result<[f32; 4], GffBinaryError> {
506 let offset = to_usize(offset, "field_data_offset")?;
507 let base = parser
508 .header
509 .field_data_offset
510 .checked_add(offset)
511 .ok_or_else(|| GffBinaryError::InvalidData("vector4 base overflow".into()))?;
512 let end_rel = offset
513 .checked_add(16)
514 .ok_or_else(|| GffBinaryError::InvalidData("vector4 end overflow".into()))?;
515 if end_rel > parser.header.field_data_count {
516 return Err(GffBinaryError::InvalidData(
517 "vector4 exceeds field data bounds".into(),
518 ));
519 }
520 Ok([
521 binary::read_f32(parser.bytes, base)?,
522 binary::read_f32(parser.bytes, base + 4)?,
523 binary::read_f32(parser.bytes, base + 8)?,
524 binary::read_f32(parser.bytes, base + 12)?,
525 ])
526}
527
528fn read_vector3(parser: &GffParser<'_>, offset: u32) -> Result<[f32; 3], GffBinaryError> {
529 let offset = to_usize(offset, "field_data_offset")?;
530 let base = parser
531 .header
532 .field_data_offset
533 .checked_add(offset)
534 .ok_or_else(|| GffBinaryError::InvalidData("vector3 base overflow".into()))?;
535 let end_rel = offset
536 .checked_add(12)
537 .ok_or_else(|| GffBinaryError::InvalidData("vector3 end overflow".into()))?;
538 if end_rel > parser.header.field_data_count {
539 return Err(GffBinaryError::InvalidData(
540 "vector3 exceeds field data bounds".into(),
541 ));
542 }
543 Ok([
544 binary::read_f32(parser.bytes, base)?,
545 binary::read_f32(parser.bytes, base + 4)?,
546 binary::read_f32(parser.bytes, base + 8)?,
547 ])
548}
549
550fn read_struct_list(
551 parser: &GffParser<'_>,
552 offset: u32,
553 depth: usize,
554) -> Result<Vec<GffStruct>, GffBinaryError> {
555 let offset = to_usize(offset, "list_indices_offset")?;
556 let count_base = parser
557 .header
558 .list_indices_offset
559 .checked_add(offset)
560 .ok_or_else(|| GffBinaryError::InvalidData("list base overflow".into()))?;
561 if offset
562 .checked_add(4)
563 .is_none_or(|end| end > parser.header.list_indices_count)
564 {
565 return Err(GffBinaryError::InvalidData(
566 "list count read out of range".into(),
567 ));
568 }
569 let count = to_usize(binary::read_u32(parser.bytes, count_base)?, "list_count")?;
570 let list_entries_size = count
571 .checked_mul(4)
572 .ok_or_else(|| GffBinaryError::InvalidData("list entries size overflow".into()))?;
573 if offset
574 .checked_add(4)
575 .and_then(|v| v.checked_add(list_entries_size))
576 .is_none_or(|end| end > parser.header.list_indices_count)
577 {
578 return Err(GffBinaryError::InvalidData(
579 "list entries out of range".into(),
580 ));
581 }
582 let mut out = Vec::with_capacity(count);
583 let entries_base = count_base + 4;
584 for index in 0..count {
585 let struct_index = to_usize(
586 binary::read_u32(parser.bytes, entries_base + index * 4)?,
587 "list_struct_index",
588 )?;
589 out.push(parser.read_struct(struct_index, depth + 1)?);
590 }
591 Ok(out)
592}
593
594fn read_localized_string(
595 parser: &GffParser<'_>,
596 offset: u32,
597 field_index: usize,
598) -> Result<GffLocalizedString, GffBinaryError> {
599 let offset = to_usize(offset, "field_data_offset")?;
600 let base = parser
601 .header
602 .field_data_offset
603 .checked_add(offset)
604 .ok_or_else(|| GffBinaryError::InvalidData("locstring base overflow".into()))?;
605 let total_size = to_usize(
606 binary::read_u32(parser.bytes, base)?,
607 "locstring_total_size",
608 )?;
609 let payload_rel_start = offset
610 .checked_add(4)
611 .ok_or_else(|| GffBinaryError::InvalidData("locstring payload start overflow".into()))?;
612 let payload_rel_end = payload_rel_start
613 .checked_add(total_size)
614 .ok_or_else(|| GffBinaryError::InvalidData("locstring payload end overflow".into()))?;
615 if payload_rel_end > parser.header.field_data_count {
616 return Err(GffBinaryError::InvalidData(format!(
617 "locstring payload out of range for field[{field_index}]"
618 )));
619 }
620
621 let payload_start = base + 4;
622 let string_ref = StrRef::from_raw(read_i32(parser.bytes, payload_start)?);
623 let substring_count = to_usize(
624 binary::read_u32(parser.bytes, payload_start + 4)?,
625 "substring_count",
626 )?;
627 let mut cursor = payload_start + 8;
628 let payload_end = payload_start + total_size;
629 let mut substrings = Vec::with_capacity(substring_count);
630
631 for substring_index in 0..substring_count {
632 if cursor.checked_add(8).is_none_or(|end| end > payload_end) {
633 return Err(GffBinaryError::InvalidData(format!(
634 "locstring header truncated at substring {substring_index}"
635 )));
636 }
637 let string_id = binary::read_u32(parser.bytes, cursor)?;
638 let length = to_usize(
639 binary::read_u32(parser.bytes, cursor + 4)?,
640 "substring_length",
641 )?;
642 cursor += 8;
643 if cursor
644 .checked_add(length)
645 .is_none_or(|end| end > payload_end)
646 {
647 return Err(GffBinaryError::InvalidData(format!(
648 "locstring substring bytes out of range at index {substring_index}"
649 )));
650 }
651 let bytes = parser.bytes.get(cursor..cursor + length).ok_or_else(|| {
652 GffBinaryError::InvalidData("locstring substring slice invalid".into())
653 })?;
654 let language_id = string_id / 2;
655 let encoding = text_encoding_for_language(language_id)
656 .map_err(|err| GffBinaryError::UnsupportedLanguageEncoding(err.language_id.raw()))?;
660 let text =
661 decode_text_strict(bytes, encoding).map_err(|source| GffBinaryError::TextDecoding {
662 context: format!("field[{field_index}] locstring[{substring_index}]"),
663 source,
664 })?;
665 substrings.push(GffLocalizedSubstring { string_id, text });
666 cursor += length;
667 }
668
669 if cursor != payload_end {
670 return Err(GffBinaryError::InvalidData(format!(
671 "locstring payload has {} trailing bytes",
672 payload_end - cursor
673 )));
674 }
675
676 Ok(GffLocalizedString {
677 string_ref,
678 substrings,
679 })
680}
681
682fn check_table_bounds(
683 total_len: usize,
684 offset: usize,
685 size: usize,
686 table_name: &str,
687) -> Result<(), GffBinaryError> {
688 binary::check_range_in_bounds(total_len, offset, size, table_name)?;
689 Ok(())
690}
691
692fn read_i32(bytes: &[u8], offset: usize) -> Result<i32, GffBinaryError> {
693 let bits = binary::read_u32(bytes, offset)?;
694 Ok(i32::from_le_bytes(bits.to_le_bytes()))
695}
696
697#[cfg(test)]
698mod tests {
699 use super::*;
700 use crate::gff::write_gff_to_vec;
701 use crate::gff_label;
702
703 const TEST_GFF: &[u8] = include_bytes!(concat!(
704 env!("CARGO_MANIFEST_DIR"),
705 "/../../fixtures/test.gff"
706 ));
707 const TEST_UTC: &[u8] = include_bytes!(concat!(
708 env!("CARGO_MANIFEST_DIR"),
709 "/../../fixtures/test.utc"
710 ));
711
712 #[test]
713 fn roundtrip_gff_binary_with_all_core_field_variants() {
714 let mut root = GffStruct::new(-1);
715 root.push_field(gff_label!("uint8"), GffValue::UInt8(255));
716 root.push_field(gff_label!("int8"), GffValue::Int8(-127));
717 root.push_field(gff_label!("uint16"), GffValue::UInt16(65535));
718 root.push_field(gff_label!("int16"), GffValue::Int16(-32768));
719 root.push_field(gff_label!("uint32"), GffValue::UInt32(u32::MAX));
720 root.push_field(gff_label!("int32"), GffValue::Int32(i32::MIN));
721 root.push_field(gff_label!("uint64"), GffValue::UInt64(4_294_967_296));
722 root.push_field(gff_label!("int64"), GffValue::Int64(2_147_483_647));
723 root.push_field(gff_label!("single"), GffValue::Single(12.34567));
724 root.push_field(gff_label!("double"), GffValue::Double(12.345678901234));
725 root.push_field(
726 gff_label!("string"),
727 GffValue::String("abcdefghij123456789".into()),
728 );
729 root.push_field(gff_label!("resref"), GffValue::resref_lit("resref01"));
730 root.push_field(
731 gff_label!("locstring"),
732 GffValue::LocalizedString(GffLocalizedString {
733 string_ref: StrRef::invalid(),
734 substrings: vec![
735 GffLocalizedSubstring {
736 string_id: 0,
737 text: "male_eng".into(),
738 },
739 GffLocalizedSubstring {
740 string_id: 5,
741 text: "fem_german".into(),
742 },
743 ],
744 }),
745 );
746 root.push_field(
747 gff_label!("binary"),
748 GffValue::Binary(b"binarydata".to_vec()),
749 );
750 root.push_field(
751 gff_label!("orientation"),
752 GffValue::Vector4([1.0, 2.0, 3.0, 4.0]),
753 );
754 root.push_field(
755 gff_label!("position"),
756 GffValue::Vector3([11.0, 22.0, 33.0]),
757 );
758
759 let mut child = GffStruct::new(0);
760 child.push_field(gff_label!("child_uint8"), GffValue::UInt8(4));
761 root.push_field(
762 gff_label!("child_struct"),
763 GffValue::Struct(Box::new(child)),
764 );
765 root.push_field(
766 gff_label!("list"),
767 GffValue::List(vec![GffStruct::new(1), GffStruct::new(2)]),
768 );
769
770 let original = Gff::generic(root);
771 let bytes = write_gff_to_vec(&original).expect("write should succeed");
772 let parsed = read_gff_from_bytes(&bytes).expect("read should succeed");
773 assert_eq!(parsed, original);
774 }
775
776 #[test]
777 fn parses_gff_fixture() {
778 let gff = read_gff_from_bytes(TEST_GFF).expect("fixture should parse");
779 assert_eq!(gff.file_type, *b"GFF ");
780
781 assert_eq!(find_field(&gff.root, "uint8"), &GffValue::UInt8(255));
782 assert_eq!(find_field(&gff.root, "int8"), &GffValue::Int8(-127));
783 assert_eq!(find_field(&gff.root, "uint16"), &GffValue::UInt16(65535));
784 assert_eq!(find_field(&gff.root, "int16"), &GffValue::Int16(-32768));
785 assert_eq!(find_field(&gff.root, "uint32"), &GffValue::UInt32(u32::MAX));
786 assert_eq!(find_field(&gff.root, "int32"), &GffValue::Int32(i32::MIN));
787 assert_eq!(
788 find_field(&gff.root, "uint64"),
789 &GffValue::UInt64(4_294_967_296)
790 );
791 assert_eq!(
792 find_field(&gff.root, "string"),
793 &GffValue::String("abcdefghij123456789".into())
794 );
795 assert_eq!(
796 find_field(&gff.root, "resref"),
797 &GffValue::resref_lit("resref01")
798 );
799 match find_field(&gff.root, "locstring") {
800 GffValue::LocalizedString(loc) => {
801 assert_eq!(loc.string_ref, StrRef::invalid());
802 assert_eq!(loc.substrings.len(), 2);
803 assert_eq!(loc.substrings[0].text, "male_eng");
804 assert_eq!(loc.substrings[1].text, "fem_german");
805 }
806 other => panic!("expected localized string, got {other:?}"),
807 }
808 }
809
810 #[test]
811 fn read_write_roundtrip_preserves_fixture_semantics() {
812 let parsed = read_gff_from_bytes(TEST_GFF).expect("read should succeed");
813 let bytes = write_gff_to_vec(&parsed).expect("write should succeed");
814 let reparsed = read_gff_from_bytes(&bytes).expect("re-read should succeed");
815 assert_eq!(reparsed, parsed);
816 }
817
818 #[test]
819 fn writer_is_deterministic_for_parsed_fixture() {
820 let parsed = read_gff_from_bytes(TEST_GFF).expect("fixture should parse");
821 let first = write_gff_to_vec(&parsed).expect("first write should succeed");
822 let second = write_gff_to_vec(&parsed).expect("second write should succeed");
823 assert_eq!(first, second, "canonical GFF writer output drifted");
824 }
825
826 #[test]
827 fn roundtrip_preserves_list_order_and_struct_ids() {
828 let mut first = GffStruct::new(500);
829 first.push_field(gff_label!("marker"), GffValue::UInt16(11));
830
831 let mut second = GffStruct::new(2);
832 second.push_field(gff_label!("marker"), GffValue::UInt16(22));
833
834 let mut third = GffStruct::new(9_999);
835 third.push_field(gff_label!("marker"), GffValue::UInt16(33));
836
837 let mut root = GffStruct::new(-1);
838 root.push_field(
839 gff_label!("ordered"),
840 GffValue::List(vec![first, second, third]),
841 );
842
843 let gff = Gff::generic(root);
844 let bytes = write_gff_to_vec(&gff).expect("write should succeed");
845 let reparsed = read_gff_from_bytes(&bytes).expect("read should succeed");
846
847 let list = find_list(&reparsed.root, "ordered");
848 assert_eq!(list_struct_ids(list), vec![500, 2, 9_999]);
849 assert_eq!(list_u16_field(list, "marker"), vec![11, 22, 33]);
850 }
851
852 #[test]
853 fn utc_fixture_roundtrip_preserves_list_indices_and_values() {
854 let parsed = read_gff_from_bytes(TEST_UTC).expect("fixture should parse");
855 let bytes = write_gff_to_vec(&parsed).expect("write should succeed");
856 let reparsed = read_gff_from_bytes(&bytes).expect("re-read should succeed");
857
858 for label in ["FeatList", "Equip_ItemList", "ItemList", "ClassList"] {
859 assert_eq!(
860 list_struct_ids(find_list(&parsed.root, label)),
861 list_struct_ids(find_list(&reparsed.root, label)),
862 "list struct_id order changed for {label}"
863 );
864 }
865
866 assert_eq!(
867 list_u16_field(find_list(&parsed.root, "FeatList"), "Feat"),
868 list_u16_field(find_list(&reparsed.root, "FeatList"), "Feat")
869 );
870 assert_eq!(
871 list_resref_field(find_list(&parsed.root, "Equip_ItemList"), "EquippedRes"),
872 list_resref_field(find_list(&reparsed.root, "Equip_ItemList"), "EquippedRes")
873 );
874 assert_eq!(
875 list_resref_field(find_list(&parsed.root, "ItemList"), "InventoryRes"),
876 list_resref_field(find_list(&reparsed.root, "ItemList"), "InventoryRes")
877 );
878 }
879
880 #[test]
881 fn rejects_invalid_version() {
882 let mut bytes = vec![0_u8; GFF_HEADER_SIZE];
883 bytes[0..4].copy_from_slice(b"GFF ");
884 bytes[4..8].copy_from_slice(b"V9.9");
885 let err = read_gff_from_bytes(&bytes).expect_err("must fail");
886 assert!(matches!(err, GffBinaryError::InvalidVersion(_)));
887 }
888
889 #[test]
890 fn rejects_truncated_header() {
891 let bytes = vec![0_u8; GFF_HEADER_SIZE - 1];
892 let err = read_gff_from_bytes(&bytes).expect_err("must fail");
893 assert!(matches!(err, GffBinaryError::InvalidHeader(_)));
894 }
895
896 #[test]
897 fn rejects_unknown_field_type() {
898 let mut bytes = TEST_GFF.to_vec();
899 let field_offset = usize::try_from(u32::from_le_bytes(
900 bytes[16..20].try_into().expect("field offset bytes"),
901 ))
902 .expect("offset fits in usize");
903 bytes[field_offset..field_offset + 4].copy_from_slice(&99_u32.to_le_bytes());
904
905 let err = read_gff_from_bytes(&bytes).expect_err("must fail");
906 assert!(matches!(err, GffBinaryError::InvalidFieldType(99)));
907 }
908
909 #[test]
910 fn surfaces_field_type_eighteen_rather_than_guessing() {
911 let mut bytes = TEST_GFF.to_vec();
916 let field_offset = usize::try_from(u32::from_le_bytes(
917 bytes[16..20].try_into().expect("field offset bytes"),
918 ))
919 .expect("offset fits in usize");
920 bytes[field_offset..field_offset + 4].copy_from_slice(&18_u32.to_le_bytes());
921
922 let err = read_gff_from_bytes(&bytes).expect_err("must fail");
923 assert!(matches!(err, GffBinaryError::InvalidFieldType(18)));
924 }
925
926 #[test]
927 fn accepts_two_struct_gff_with_nested_list_entry() {
928 let mut entry = GffStruct::new(0);
935 entry.push_field(gff_label!("PropertyName"), GffValue::UInt16(11));
936 entry.push_field(gff_label!("Subtype"), GffValue::UInt16(5));
937
938 let mut root = GffStruct::new(-1);
939 root.push_field(gff_label!("PropertiesList"), GffValue::List(vec![entry]));
940
941 let gff = Gff::generic(root);
942 let bytes = write_gff_to_vec(&gff).expect("write should succeed");
943 let parsed = read_gff_from_bytes(&bytes).expect("two-struct GFF should parse");
944
945 let GffValue::List(list) = find_field(&parsed.root, "PropertiesList") else {
946 panic!("PropertiesList should be a list");
947 };
948 assert_eq!(list.len(), 1);
949 assert_eq!(list[0].field("PropertyName"), Some(&GffValue::UInt16(11)));
950 }
951
952 #[test]
953 fn writer_rejects_unsupported_locstring_language_ids() {
954 let mut root = GffStruct::new(-1);
955 root.push_field(
956 gff_label!("locstring"),
957 GffValue::LocalizedString(GffLocalizedString {
958 string_ref: StrRef::invalid(),
959 substrings: vec![GffLocalizedSubstring {
960 string_id: 140,
961 text: "test".into(),
962 }],
963 }),
964 );
965 let gff = Gff::generic(root);
966
967 let err = write_gff_to_vec(&gff).expect_err("must fail");
968 assert!(matches!(
969 err,
970 GffBinaryError::UnsupportedLanguageEncoding(70)
971 ));
972 }
973
974 fn find_field<'a>(structure: &'a GffStruct, label: &str) -> &'a GffValue {
975 structure
976 .field(label)
977 .unwrap_or_else(|| panic!("missing field {label}"))
978 }
979
980 fn find_list<'a>(structure: &'a GffStruct, label: &str) -> &'a [GffStruct] {
981 match structure.field(label) {
982 Some(GffValue::List(values)) => values.as_slice(),
983 Some(other) => panic!("field {label} is not a list: {other:?}"),
984 None => panic!("missing list field {label}"),
985 }
986 }
987
988 fn list_struct_ids(list: &[GffStruct]) -> Vec<i32> {
989 list.iter().map(|entry| entry.struct_id).collect::<Vec<_>>()
990 }
991
992 fn list_u16_field(list: &[GffStruct], label: &str) -> Vec<u16> {
993 list.iter()
994 .map(|entry| match entry.field(label) {
995 Some(GffValue::UInt16(value)) => *value,
996 Some(other) => panic!("field {label} is not UInt16: {other:?}"),
997 None => panic!("missing field {label}"),
998 })
999 .collect::<Vec<_>>()
1000 }
1001
1002 fn list_resref_field(list: &[GffStruct], label: &str) -> Vec<String> {
1003 list.iter()
1004 .map(|entry| match entry.field(label) {
1005 Some(GffValue::ResRef(value)) => value.to_string(),
1006 Some(other) => panic!("field {label} is not ResRef: {other:?}"),
1007 None => panic!("missing field {label}"),
1008 })
1009 .collect::<Vec<_>>()
1010 }
1011}