Skip to main content

rakata_formats/gff/
coerce.rs

1//! Typed reads and writes of a `GffStruct`'s fields, by label.
2//!
3//! A GFF integer field is not stored at one width: the same logical value can
4//! arrive as any of several encodings depending on what wrote the file, so a
5//! consumer wanting an `i16` has to accept every encoding that can hold one.
6//! These centralise that coercion so each view is not carrying its own.
7//!
8//! They live here rather than in a consumer because they are a GFF-layer
9//! concern: reading an `i16` out of any compatible encoding is a property of
10//! the format, not of any one resource type that uses it. Both crates that
11//! model GFF resources need them, and a macro expanded in either has to be
12//! able to name what it calls.
13
14use super::{GffLabel, GffLocalizedString, GffStruct, GffValue};
15use rakata_core::ResRef;
16
17/// Updates an existing field value or inserts a new field when missing.
18///
19/// Updates the first field carrying `label`. On a struct that holds the label
20/// more than once, such as a `.dlg` node's six `SoundExists` copies, the others
21/// keep the value they had.
22pub fn upsert_field(structure: &mut GffStruct, label: GffLabel, value: GffValue) {
23    if let Some(field) = structure
24        .fields
25        .iter_mut()
26        .find(|field| field.label == label)
27    {
28        field.value = value;
29    } else {
30        structure.push_field(label, value);
31    }
32}
33
34/// Reads a string field.
35pub fn get_string(structure: &GffStruct, label: &str) -> Option<String> {
36    match structure.field(label) {
37        Some(GffValue::String(value)) => Some(value.clone()),
38        _ => None,
39    }
40}
41
42/// Reads a `ResRef` field.
43pub fn get_resref(structure: &GffStruct, label: &str) -> Option<ResRef> {
44    match structure.field(label) {
45        Some(GffValue::ResRef(value)) => Some(*value),
46        _ => None,
47    }
48}
49
50/// Reads a localized-string field by reference.
51pub fn get_locstring<'a>(structure: &'a GffStruct, label: &str) -> Option<&'a GffLocalizedString> {
52    match structure.field(label) {
53        Some(GffValue::LocalizedString(value)) => Some(value),
54        _ => None,
55    }
56}
57
58/// Reads an `i8` from compatible integer field encodings.
59pub fn get_i8(structure: &GffStruct, label: &str) -> Option<i8> {
60    match structure.field(label) {
61        Some(GffValue::Int8(value)) => Some(*value),
62        Some(GffValue::UInt8(value)) => i8::try_from(*value).ok(),
63        Some(GffValue::Int16(value)) => i8::try_from(*value).ok(),
64        Some(GffValue::UInt16(value)) => i8::try_from(*value).ok(),
65        Some(GffValue::Int32(value)) => i8::try_from(*value).ok(),
66        Some(GffValue::UInt32(value)) => i8::try_from(*value).ok(),
67        _ => None,
68    }
69}
70
71/// Reads an `i16` from compatible integer field encodings.
72pub fn get_i16(structure: &GffStruct, label: &str) -> Option<i16> {
73    match structure.field(label) {
74        Some(GffValue::Int16(value)) => Some(*value),
75        Some(GffValue::UInt16(value)) => i16::try_from(*value).ok(),
76        Some(GffValue::Int8(value)) => Some(i16::from(*value)),
77        Some(GffValue::UInt8(value)) => Some(i16::from(*value)),
78        Some(GffValue::Int32(value)) => i16::try_from(*value).ok(),
79        Some(GffValue::UInt32(value)) => i16::try_from(*value).ok(),
80        _ => None,
81    }
82}
83
84/// Reads an `i32` from compatible integer field encodings.
85pub fn get_i32(structure: &GffStruct, label: &str) -> Option<i32> {
86    match structure.field(label) {
87        Some(GffValue::Int32(value)) => Some(*value),
88        Some(GffValue::UInt32(value)) => i32::try_from(*value).ok(),
89        Some(GffValue::Int16(value)) => Some(i32::from(*value)),
90        Some(GffValue::UInt16(value)) => Some(i32::from(*value)),
91        Some(GffValue::Int8(value)) => Some(i32::from(*value)),
92        Some(GffValue::UInt8(value)) => Some(i32::from(*value)),
93        _ => None,
94    }
95}
96
97/// Reads a `u8` from compatible integer field encodings.
98pub fn get_u8(structure: &GffStruct, label: &str) -> Option<u8> {
99    match structure.field(label) {
100        Some(GffValue::UInt8(value)) => Some(*value),
101        Some(GffValue::Int8(value)) => u8::try_from(*value).ok(),
102        Some(GffValue::UInt16(value)) => u8::try_from(*value).ok(),
103        Some(GffValue::Int16(value)) => u8::try_from(*value).ok(),
104        Some(GffValue::UInt32(value)) => u8::try_from(*value).ok(),
105        Some(GffValue::Int32(value)) => u8::try_from(*value).ok(),
106        _ => None,
107    }
108}
109
110/// Reads a `u16` from compatible integer field encodings.
111pub fn get_u16(structure: &GffStruct, label: &str) -> Option<u16> {
112    match structure.field(label) {
113        Some(GffValue::UInt16(value)) => Some(*value),
114        Some(GffValue::Int16(value)) => u16::try_from(*value).ok(),
115        Some(GffValue::UInt8(value)) => Some(u16::from(*value)),
116        Some(GffValue::Int8(value)) => u16::try_from(*value).ok(),
117        Some(GffValue::UInt32(value)) => u16::try_from(*value).ok(),
118        Some(GffValue::Int32(value)) => u16::try_from(*value).ok(),
119        _ => None,
120    }
121}
122
123/// Reads a `u32` from canonical integer encodings.
124pub fn get_u32(structure: &GffStruct, label: &str) -> Option<u32> {
125    match structure.field(label) {
126        Some(GffValue::UInt32(value)) => Some(*value),
127        Some(GffValue::Int32(value)) => u32::try_from(*value).ok(),
128        Some(GffValue::UInt16(value)) => Some(u32::from(*value)),
129        Some(GffValue::UInt8(value)) => Some(u32::from(*value)),
130        _ => None,
131    }
132}
133
134/// Reads a `u32` with additional signed-narrow coercions for compatibility.
135pub fn get_u32_extended_signed(structure: &GffStruct, label: &str) -> Option<u32> {
136    match structure.field(label) {
137        Some(GffValue::UInt32(value)) => Some(*value),
138        Some(GffValue::Int32(value)) => u32::try_from(*value).ok(),
139        Some(GffValue::UInt16(value)) => Some(u32::from(*value)),
140        Some(GffValue::Int16(value)) => u32::try_from(*value).ok(),
141        Some(GffValue::UInt8(value)) => Some(u32::from(*value)),
142        Some(GffValue::Int8(value)) => u32::try_from(*value).ok(),
143        _ => None,
144    }
145}
146
147/// Reads an `f32` from single/double fields.
148pub fn get_f32(structure: &GffStruct, label: &str) -> Option<f32> {
149    match structure.field(label) {
150        Some(GffValue::Single(value)) => Some(*value),
151        // Intentional precision narrowing: GFF Double -> f32 for typed model fields.
152        #[allow(clippy::cast_possible_truncation, clippy::as_conversions)]
153        Some(GffValue::Double(value)) => Some(*value as f32),
154        _ => None,
155    }
156}
157
158/// Reads a `VOID` field's bytes.
159///
160/// No coercion ladder: nothing else in the format is a byte string, so a
161/// field that is not `VOID` has no reading here.
162pub fn get_binary<'a>(structure: &'a GffStruct, label: &str) -> Option<&'a [u8]> {
163    match structure.field(label) {
164        Some(GffValue::Binary(bytes)) => Some(bytes),
165        _ => None,
166    }
167}
168
169/// Reads a `u64` from compatible integer field encodings.
170pub fn get_u64(structure: &GffStruct, label: &str) -> Option<u64> {
171    match structure.field(label) {
172        Some(GffValue::UInt64(value)) => Some(*value),
173        Some(GffValue::UInt32(value)) => Some(u64::from(*value)),
174        Some(GffValue::Int64(value)) => u64::try_from(*value).ok(),
175        Some(GffValue::Int32(value)) => u64::try_from(*value).ok(),
176        Some(GffValue::UInt16(value)) => Some(u64::from(*value)),
177        Some(GffValue::UInt8(value)) => Some(u64::from(*value)),
178        _ => None,
179    }
180}
181
182/// Reads a boolean from numeric fields (non-zero is true).
183pub fn get_bool(structure: &GffStruct, label: &str) -> Option<bool> {
184    match structure.field(label) {
185        Some(GffValue::UInt8(value)) => Some(*value != 0),
186        Some(GffValue::Int8(value)) => Some(*value != 0),
187        Some(GffValue::UInt16(value)) => Some(*value != 0),
188        Some(GffValue::Int16(value)) => Some(*value != 0),
189        Some(GffValue::UInt32(value)) => Some(*value != 0),
190        Some(GffValue::Int32(value)) => Some(*value != 0),
191        _ => None,
192    }
193}