ssbh_lib/
strings.rs

1use crate::RelPtr64;
2use binrw::BinRead;
3use ssbh_write::SsbhWrite;
4use std::{io::Read, str::FromStr};
5
6#[cfg(feature = "serde")]
7use serde::{Deserialize, Deserializer, Serialize, Serializer};
8
9/// An N-byte aligned [CString] with position determined by a relative offset.
10#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
11#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
12#[derive(Debug, BinRead, SsbhWrite, PartialEq, Eq, Clone)]
13pub struct SsbhStringN<const N: usize>(RelPtr64<CString<N>>);
14
15/// A 4-byte aligned [CString] with position determined by a relative offset.
16pub type SsbhString = SsbhStringN<4>;
17
18/// An 8-byte aligned [CString] with position determined by a relative offset.
19pub type SsbhString8 = SsbhStringN<8>;
20
21/// A null terminated string without additional alignment requirements.
22pub type CString1 = CString<1>;
23
24/// A null terminated string with a specified alignment.
25/// The empty string is represented as `N` null bytes.
26#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
27#[derive(Debug, PartialEq, Eq, Clone)]
28pub struct CString<const N: usize>(
29    // Don't make this public to prevent inserting null bytes.
30    #[cfg_attr(
31        feature = "serde",
32        serde(
33            serialize_with = "serialize_str_bytes",
34            deserialize_with = "deserialize_str_bytes"
35        )
36    )]
37    Vec<u8>,
38);
39
40impl<const N: usize> CString<N> {
41    /// Creates the string by reading from `bytes` until the first null byte.
42    pub fn from_bytes(bytes: &[u8]) -> Self {
43        Self(bytes.iter().copied().take_while(|b| *b != 0u8).collect())
44    }
45
46    /// Converts the underlying buffer to a [str].
47    /// The result will be [None] if the the conversion failed.
48    pub fn to_str(&self) -> Option<&str> {
49        std::str::from_utf8(&self.0).ok()
50    }
51
52    /// Converts the underlying buffer to a [String].
53    pub fn to_string_lossy(&self) -> String {
54        self.to_str().unwrap_or("").to_string()
55    }
56}
57
58impl<const N: usize> BinRead for CString<N> {
59    type Args<'a> = ();
60
61    fn read_options<R: Read + std::io::Seek>(
62        reader: &mut R,
63        _endian: binrw::Endian,
64        _args: Self::Args<'_>,
65    ) -> binrw::BinResult<Self> {
66        // This should terminate when the reader runs out of bytes.
67        let mut bytes = Vec::new();
68        loop {
69            let b = u8::read(reader)?;
70            if b == 0 {
71                return Ok(Self(bytes));
72            }
73            bytes.push(b);
74        }
75    }
76}
77
78#[cfg(feature = "serde")]
79fn serialize_str_bytes<S>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error>
80where
81    S: Serializer,
82{
83    let text = CString::<1>::from_bytes(bytes).to_string_lossy();
84    serializer.serialize_str(&text)
85}
86
87#[cfg(feature = "serde")]
88fn deserialize_str_bytes<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
89where
90    D: Deserializer<'de>,
91{
92    let string = String::deserialize(deserializer)?;
93
94    // TODO: This should check for null bytes?
95    Ok(string.as_bytes().to_vec())
96}
97
98#[cfg(feature = "arbitrary")]
99impl<'a, const N: usize> arbitrary::Arbitrary<'a> for CString<N> {
100    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
101        let bytes = Vec::<u8>::arbitrary(u)?;
102        Ok(Self::from_bytes(&bytes))
103    }
104}
105
106impl<const N: usize> FromStr for CString<N> {
107    type Err = core::convert::Infallible;
108
109    fn from_str(s: &str) -> Result<Self, Self::Err> {
110        Ok(s.into())
111    }
112}
113
114impl<const N: usize> From<&str> for CString<N> {
115    fn from(text: &str) -> Self {
116        Self::from_bytes(text.as_bytes())
117    }
118}
119
120impl<const N: usize> From<&String> for CString<N> {
121    fn from(text: &String) -> Self {
122        Self::from_bytes(text.as_bytes())
123    }
124}
125
126impl<const N: usize> From<String> for CString<N> {
127    fn from(text: String) -> Self {
128        Self::from_bytes(text.as_bytes())
129    }
130}
131
132impl<const N: usize> crate::SsbhWrite for CString<N> {
133    fn ssbh_write<W: std::io::Write + std::io::Seek>(
134        &self,
135        writer: &mut W,
136        _data_ptr: &mut u64,
137    ) -> std::io::Result<()> {
138        if self.0.is_empty() {
139            // Handle empty strings.
140            writer.write_all(&[0u8; N])?;
141        } else {
142            // Write the data and null terminator.
143            writer.write_all(&self.0)?;
144            writer.write_all(&[0u8])?;
145        }
146        Ok(())
147    }
148
149    fn size_in_bytes(&self) -> u64 {
150        self.0.size_in_bytes()
151    }
152
153    fn alignment_in_bytes() -> u64 {
154        N as u64
155    }
156}
157
158impl<const N: usize> SsbhStringN<N> {
159    /// Creates the string by reading from `bytes` until the first null byte.
160    pub fn from_bytes(bytes: &[u8]) -> Self {
161        Self(RelPtr64::new(CString::from_bytes(bytes)))
162    }
163
164    /// Converts the underlying buffer to a [str].
165    /// The result will be [None] if the offset is null or the conversion failed.
166    pub fn to_str(&self) -> Option<&str> {
167        self.0.as_ref()?.to_str()
168    }
169
170    /// Converts the underlying buffer to a [String].
171    /// Empty or null values are converted to empty strings.
172    pub fn to_string_lossy(&self) -> String {
173        self.to_str().unwrap_or("").to_string()
174    }
175}
176
177impl<const N: usize> FromStr for SsbhStringN<N> {
178    type Err = core::convert::Infallible;
179
180    fn from_str(s: &str) -> Result<Self, Self::Err> {
181        Ok(s.into())
182    }
183}
184
185impl<const N: usize> From<&str> for SsbhStringN<N> {
186    fn from(text: &str) -> Self {
187        Self::from_bytes(text.as_bytes())
188    }
189}
190
191impl<const N: usize> From<&String> for SsbhStringN<N> {
192    fn from(text: &String) -> Self {
193        Self::from_bytes(text.as_bytes())
194    }
195}
196
197impl<const N: usize> From<String> for SsbhStringN<N> {
198    fn from(text: String) -> Self {
199        Self::from_bytes(text.as_bytes())
200    }
201}
202
203#[cfg(test)]
204mod tests {
205    use binrw::io::Cursor;
206    use binrw::BinReaderExt;
207
208    use hexlit::hex;
209
210    use super::*;
211
212    #[test]
213    fn read_ssbh_string() {
214        let mut reader = Cursor::new(hex!(
215            "08000000 00000000 616C705F 6D617269 6F5F3030 325F636F 6C000000"
216        ));
217        let value = reader.read_le::<SsbhString>().unwrap();
218        assert_eq!("alp_mario_002_col", value.to_str().unwrap());
219
220        // Make sure the reader position is restored.
221        let value = reader.read_le::<u8>().unwrap();
222        assert_eq!(0x61u8, value);
223    }
224
225    #[test]
226    fn read_ssbh_string_empty() {
227        let mut reader = Cursor::new(hex!("08000000 00000000 00000000"));
228        let value = reader.read_le::<SsbhString>().unwrap();
229        assert_eq!("", value.to_str().unwrap());
230
231        // Make sure the reader position is restored.
232        let value = reader.read_le::<u8>().unwrap();
233        assert_eq!(0u8, value);
234    }
235
236    #[test]
237    fn read_ssbh_string_missing_null() {
238        // This should be an EOF error rather than an empty string.
239        let mut reader = Cursor::new(hex!("08000000 FFFFFFFF"));
240        let result = reader.read_le::<SsbhString>();
241        assert!(result.is_err());
242    }
243
244    #[test]
245    fn cstring_to_string_conversion() {
246        assert_eq!(Some("abc"), CString::<4>::from_bytes(b"abc\0").to_str());
247        assert_eq!(
248            "abc".to_string(),
249            CString::<4>::from_bytes(b"abc\0").to_string_lossy()
250        );
251    }
252
253    #[test]
254    fn ssbh_string_to_string_conversion() {
255        assert_eq!(Some("abc"), SsbhString::from("abc").to_str());
256        assert_eq!("abc".to_string(), SsbhString::from("abc").to_string_lossy());
257    }
258
259    #[test]
260    fn ssbh_string8_to_string_conversion() {
261        assert_eq!(Some("abc"), SsbhString8::from("abc").to_str());
262        assert_eq!(
263            "abc".to_string(),
264            SsbhString8::from("abc").to_string_lossy()
265        );
266    }
267
268    #[test]
269    fn ssbh_string_from_str() {
270        let s = SsbhString::from_str("abc").unwrap();
271        assert_eq!("abc", s.to_str().unwrap());
272    }
273
274    #[test]
275    fn ssbh_string8_from_str() {
276        let s = SsbhString8::from_str("abc").unwrap();
277        assert_eq!("abc", s.to_str().unwrap());
278    }
279
280    #[test]
281    fn ssbh_write_string() {
282        let value = SsbhString::from("scouter1Shape");
283
284        let mut writer = Cursor::new(Vec::new());
285        let mut data_ptr = 0;
286        value.ssbh_write(&mut writer, &mut data_ptr).unwrap();
287
288        assert_eq!(
289            writer.into_inner(),
290            hex!("08000000 00000000 73636F75 74657231 53686170 6500")
291        );
292        // The data pointer should be aligned to 4.
293        assert_eq!(24, data_ptr);
294    }
295
296    #[test]
297    fn ssbh_write_string_empty() {
298        let value = SsbhString::from("");
299
300        let mut writer = Cursor::new(Vec::new());
301        let mut data_ptr = 0;
302        value.ssbh_write(&mut writer, &mut data_ptr).unwrap();
303
304        assert_eq!(writer.into_inner(), hex!("08000000 00000000 00000000"));
305        // The data pointer should be aligned to 4.
306        assert_eq!(12, data_ptr);
307    }
308
309    #[test]
310    fn ssbh_write_string_non_zero_data_ptr() {
311        let value = SsbhString::from("scouter1Shape");
312
313        let mut writer = Cursor::new(Vec::new());
314        let mut data_ptr = 5;
315        value.ssbh_write(&mut writer, &mut data_ptr).unwrap();
316
317        assert_eq!(
318            writer.into_inner(),
319            hex!("08000000 00000000 73636F75 74657231 53686170 6500")
320        );
321        // The data pointer should be aligned to 4.
322        assert_eq!(24, data_ptr);
323    }
324
325    #[test]
326    fn ssbh_write_string_tuple() {
327        #[derive(SsbhWrite)]
328        struct StringPair {
329            item1: SsbhString,
330            item2: SsbhString,
331        }
332
333        // NRPD data.
334        let value = StringPair {
335            item1: SsbhString::from("RTV_FRAME_BUFFER_COPY"),
336            item2: SsbhString::from("FB_FRAME_BUFFER_COPY"),
337        };
338
339        let mut writer = Cursor::new(Vec::new());
340        let mut data_ptr = 0;
341        value.ssbh_write(&mut writer, &mut data_ptr).unwrap();
342
343        // Check that the pointers don't overlap.
344        assert_eq!(
345            writer.into_inner(),
346            hex!(
347                "10000000 00000000 20000000 00000000 
348                 5254565F 4652414D 455F4255 46464552 
349                 5F434F50 59000000 46425F46 52414D45 
350                 5F425546 4645525F 434F5059 00"
351            )
352        );
353    }
354
355    #[test]
356    fn ssbh_write_string8() {
357        let value = SsbhString8::from("BlendState0");
358
359        let mut writer = Cursor::new(Vec::new());
360        let mut data_ptr = 0;
361        value.ssbh_write(&mut writer, &mut data_ptr).unwrap();
362
363        assert_eq!(
364            writer.into_inner(),
365            hex!("08000000 00000000 426C656E 64537461 74653000")
366        );
367        // The data pointer should be aligned to 8.
368        assert_eq!(24, data_ptr);
369    }
370
371    #[test]
372    fn ssbh_write_string8_empty() {
373        let value = SsbhString8::from("");
374
375        let mut writer = Cursor::new(Vec::new());
376        let mut data_ptr = 0;
377        value.ssbh_write(&mut writer, &mut data_ptr).unwrap();
378
379        assert_eq!(
380            writer.into_inner(),
381            hex!("08000000 00000000 00000000 00000000")
382        );
383        // The data pointer should be aligned to 8.
384        assert_eq!(16, data_ptr);
385    }
386
387    #[test]
388    fn ssbh_write_string8_non_zero_data_ptr() {
389        let value = SsbhString8::from("BlendState0");
390
391        let mut writer = Cursor::new(Vec::new());
392        let mut data_ptr = 5;
393        value.ssbh_write(&mut writer, &mut data_ptr).unwrap();
394
395        assert_eq!(
396            writer.into_inner(),
397            hex!("08000000 00000000 426C656E 64537461 74653000")
398        );
399        // The data pointer should be aligned to 8.
400        assert_eq!(24, data_ptr);
401    }
402}