physis/
common_file_operations.rs

1// SPDX-FileCopyrightText: 2024 Joshua Goins <josh@redstrate.com>
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4use binrw::{BinReaderExt, BinResult, binread};
5use half::f16;
6use std::ffi::CString;
7use std::io::SeekFrom;
8
9pub(crate) fn read_bool_from<T: std::convert::From<u8> + std::cmp::PartialEq>(x: T) -> bool {
10    x == T::from(1u8)
11}
12
13pub(crate) fn write_bool_as<T: std::convert::From<u8>>(x: &bool) -> T {
14    if *x { T::from(1u8) } else { T::from(0u8) }
15}
16
17pub(crate) fn read_string(byte_stream: Vec<u8>) -> String {
18    let str = String::from_utf8(byte_stream).unwrap();
19    str.trim_matches(char::from(0)).to_string() // trim \0 from the end of strings
20}
21
22pub(crate) fn write_string(str: &String) -> Vec<u8> {
23    let c_string = CString::new(&**str).unwrap();
24    c_string.as_bytes_with_nul().to_vec()
25}
26
27pub(crate) fn get_string_len(str: &String) -> usize {
28    let c_string = CString::new(&**str).unwrap();
29    c_string.count_bytes() + 1 // for the nul terminator
30}
31
32#[binrw::parser(reader)]
33pub(crate) fn strings_parser(
34    base_offset: u64,
35    strings_offset: &Vec<u16>,
36) -> BinResult<Vec<String>> {
37    let mut strings: Vec<String> = vec![];
38
39    for offset in strings_offset {
40        let string_offset = base_offset + *offset as u64;
41
42        let mut string = String::new();
43
44        reader.seek(SeekFrom::Start(string_offset))?;
45        let mut next_char = reader.read_le::<u8>().unwrap() as char;
46        while next_char != '\0' {
47            string.push(next_char);
48            next_char = reader.read_le::<u8>().unwrap() as char;
49        }
50
51        strings.push(string);
52    }
53
54    Ok(strings)
55}
56
57#[binrw::parser(reader)]
58pub(crate) fn string_from_offset(start: u64) -> BinResult<String> {
59    let offset: u32 = reader.read_le::<u32>()?;
60
61    let mut string = String::new();
62
63    let old_pos = reader.stream_position()?;
64
65    reader.seek(SeekFrom::Start(start + offset as u64))?;
66    reader.seek(SeekFrom::Start(start))?;
67    let mut next_char = reader.read_le::<u8>().unwrap() as char;
68    while next_char != '\0' {
69        string.push(next_char);
70        next_char = reader.read_le::<u8>().unwrap() as char;
71    }
72    reader.seek(SeekFrom::Start(old_pos))?;
73    Ok(string)
74}
75
76fn read_half1(data: [u16; 1]) -> Half1 {
77    Half1 {
78        value: f16::from_bits(data[0]),
79    }
80}
81
82#[binread]
83#[derive(Debug, Default, Clone, Copy)]
84#[br(map = read_half1)]
85pub(crate) struct Half1 {
86    pub value: f16,
87}
88
89fn read_half2(data: [u16; 2]) -> Half2 {
90    Half2 {
91        x: f16::from_bits(data[0]),
92        y: f16::from_bits(data[0]),
93    }
94}
95
96#[binread]
97#[derive(Debug, Default, Clone, Copy)]
98#[br(map = read_half2)]
99pub(crate) struct Half2 {
100    pub x: f16,
101    pub y: f16,
102}
103
104fn read_half3(data: [u16; 3]) -> Half3 {
105    Half3 {
106        r: f16::from_bits(data[0]),
107        g: f16::from_bits(data[0]),
108        b: f16::from_bits(data[0]),
109    }
110}
111
112#[binread]
113#[derive(Debug, Default, Clone, Copy)]
114#[br(map = read_half3)]
115pub(crate) struct Half3 {
116    pub r: f16,
117    pub g: f16,
118    pub b: f16,
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124
125    const DATA: [u8; 2] = [0u8, 1u8];
126
127    // TODO: add tests for u16
128
129    #[test]
130    fn read_bool_u8() {
131        assert!(!read_bool_from::<u8>(DATA[0]));
132        assert!(read_bool_from::<u8>(DATA[1]));
133    }
134
135    #[test]
136    fn write_bool_u8() {
137        assert_eq!(write_bool_as::<u8>(&false), DATA[0]);
138        assert_eq!(write_bool_as::<u8>(&true), DATA[1]);
139    }
140
141    // "FOO\0"
142    const STRING_DATA: [u8; 4] = [0x46u8, 0x4Fu8, 0x4Fu8, 0x0u8];
143
144    #[test]
145    fn read_string() {
146        // The nul terminator is supposed to be removed
147        assert_eq!(
148            crate::common_file_operations::read_string(STRING_DATA.to_vec()),
149            "FOO".to_string()
150        );
151    }
152
153    #[test]
154    fn write_string() {
155        // Supposed to include the nul terminator
156        assert_eq!(
157            crate::common_file_operations::write_string(&"FOO".to_string()),
158            STRING_DATA.to_vec()
159        );
160    }
161
162    #[test]
163    fn get_string_len() {
164        // Supposed to include the nul terminator
165        assert_eq!(
166            crate::common_file_operations::get_string_len(&"FOO".to_string()),
167            4
168        );
169    }
170}