lunify/format/
mod.rs

1#[cfg(feature = "serde")]
2use serde::{Deserialize, Serialize};
3
4mod endianness;
5mod version;
6mod width;
7
8pub use endianness::Endianness;
9pub(crate) use version::LuaVersion;
10pub use width::BitWidth;
11
12use crate::serialization::{ByteStream, ByteWriter};
13use crate::{LunifyError, Settings};
14
15/// Lua byte code format.
16#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
17#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
18pub struct Format {
19    /// The format of the compiler. The standard is 0.
20    pub format: u8,
21    /// The endianness of the target system.
22    pub endianness: Endianness,
23    /// The width of an integer on the target system.
24    pub integer_width: BitWidth,
25    /// The size of a C `type_t` on the target system. Also the pointer width.
26    pub size_t_width: BitWidth,
27    /// The size of a Lua instruction inside the binary.
28    pub instruction_width: BitWidth,
29    /// The size of the Lua number type.
30    pub number_width: BitWidth,
31    /// If a Lua number is stored as an integer or a float.
32    pub is_number_integral: bool,
33}
34
35impl Default for Format {
36    fn default() -> Self {
37        // By default we get the pointer width of the target system.
38        let size_t_width = match cfg!(target_pointer_width = "64") {
39            true => BitWidth::Bit64,
40            false => BitWidth::Bit32,
41        };
42
43        // By default we get the endianness of the target system.
44        let endianness = match unsafe { *(&1u32 as *const u32 as *const u8) } {
45            0 => Endianness::Big,
46            1 => Endianness::Little,
47            _ => unreachable!(),
48        };
49
50        Self {
51            format: 0,
52            endianness,
53            integer_width: BitWidth::Bit32,
54            size_t_width,
55            instruction_width: BitWidth::Bit32,
56            number_width: BitWidth::Bit64,
57            is_number_integral: false,
58        }
59    }
60}
61
62impl Format {
63    pub(crate) fn from_byte_stream(byte_stream: &mut ByteStream, version: LuaVersion, settings: &Settings) -> Result<Self, LunifyError> {
64        let format = match version {
65            LuaVersion::Lua51 => byte_stream.byte()?,
66            LuaVersion::Lua50 => 0,
67        };
68
69        let endianness = byte_stream.byte()?.try_into()?;
70        let integer_width = byte_stream.byte()?.try_into().map_err(LunifyError::UnsupportedIntegerWidth)?;
71        let size_t_width = byte_stream.byte()?.try_into().map_err(LunifyError::UnsupportedSizeTWidth)?;
72        let instruction_width = byte_stream.byte()?.try_into().map_err(LunifyError::UnsupportedInstructionWidth)?;
73
74        if version == LuaVersion::Lua50 {
75            let instruction_format = byte_stream.slice(4)?;
76            let expected = [
77                settings.lua50.layout.opcode.size as u8,
78                settings.lua50.layout.a.size as u8,
79                settings.lua50.layout.b.size as u8,
80                settings.lua50.layout.c.size as u8,
81            ];
82
83            if instruction_format != expected {
84                return Err(LunifyError::UnsupportedInstructionFormat(
85                    instruction_format.try_into().unwrap(),
86                ));
87            }
88        }
89
90        let number_width = byte_stream.byte()?.try_into().map_err(LunifyError::UnsupportedNumberWidth)?;
91
92        let is_number_integral = match version {
93            LuaVersion::Lua51 => byte_stream.byte()? == 1,
94            LuaVersion::Lua50 => {
95                // Is there even a way to get this information from Lua 5.0?
96                let value = from_slice!(byte_stream, number_width, endianness, f32, f64);
97                value != 31415926.535897933
98            }
99        };
100
101        #[cfg(feature = "debug")]
102        {
103            println!("format: {format}");
104            println!("endianness: {endianness:?}");
105            println!("integer_width: {integer_width}");
106            println!("size_t_width: {size_t_width}");
107            println!("instruction_width: {instruction_width}");
108            println!("number_width: {number_width}");
109            println!("is_number_integral: {is_number_integral}");
110        }
111
112        Ok(Self {
113            format,
114            endianness,
115            integer_width,
116            size_t_width,
117            instruction_width,
118            number_width,
119            is_number_integral,
120        })
121    }
122
123    pub(crate) fn write(&self, byte_writer: &mut ByteWriter) {
124        byte_writer.byte(self.format);
125        byte_writer.byte(self.endianness.into());
126        byte_writer.byte(self.integer_width.into());
127        byte_writer.byte(self.size_t_width.into());
128        byte_writer.byte(self.instruction_width.into());
129        byte_writer.byte(self.number_width.into());
130        byte_writer.byte(self.is_number_integral as u8);
131    }
132}
133
134#[cfg(test)]
135mod tests {
136    use super::LuaVersion;
137    use crate::serialization::{ByteStream, ByteWriter};
138    use crate::{BitWidth, Endianness, Format, LunifyError, Settings};
139
140    const EXPECTED_FORMAT: Format = Format {
141        format: 0,
142        endianness: Endianness::Little,
143        integer_width: BitWidth::Bit32,
144        size_t_width: BitWidth::Bit64,
145        instruction_width: BitWidth::Bit32,
146        number_width: BitWidth::Bit64,
147        is_number_integral: false,
148    };
149
150    fn from_test_data(version: LuaVersion, bytes: &[u8]) -> Result<Format, LunifyError> {
151        let mut byte_stream = ByteStream::new(bytes);
152        let result = Format::from_byte_stream(&mut byte_stream, version, &Settings::default());
153
154        assert!(byte_stream.is_empty());
155        result
156    }
157
158    #[test]
159    fn lua_51() -> Result<(), LunifyError> {
160        let result = from_test_data(LuaVersion::Lua51, &[0, 1, 4, 8, 4, 8, 0])?;
161        assert_eq!(result, EXPECTED_FORMAT);
162        Ok(())
163    }
164
165    #[test]
166    fn lua50() -> Result<(), LunifyError> {
167        let result = from_test_data(LuaVersion::Lua50, &[
168            1, 4, 8, 4, 6, 8, 9, 9, 8, 0xB6, 0x09, 0x93, 0x68, 0xE7, 0xF5, 0x7D, 0x41,
169        ])?;
170        assert_eq!(result, EXPECTED_FORMAT);
171        Ok(())
172    }
173
174    #[test]
175    fn write() {
176        let mut byter_writer = ByteWriter::new(&EXPECTED_FORMAT);
177        EXPECTED_FORMAT.write(&mut byter_writer);
178        assert_eq!(byter_writer.finalize(), [0, 1, 4, 8, 4, 8, 0]);
179    }
180
181    #[test]
182    fn lua50_unsupported_instruction_format() {
183        let result = from_test_data(LuaVersion::Lua50, &[1, 4, 8, 4, 6, 9, 8, 9]);
184        assert_eq!(result, Err(LunifyError::UnsupportedInstructionFormat([6, 9, 8, 9])));
185    }
186
187    #[test]
188    fn unsupported_integer_width() {
189        let result = from_test_data(LuaVersion::Lua51, &[0, 1, 6]);
190        assert_eq!(result, Err(LunifyError::UnsupportedIntegerWidth(6)));
191    }
192
193    #[test]
194    fn unsupported_size_t_width() {
195        let result = from_test_data(LuaVersion::Lua51, &[0, 1, 4, 6]);
196        assert_eq!(result, Err(LunifyError::UnsupportedSizeTWidth(6)));
197    }
198
199    #[test]
200    fn unsupported_instruction_width() {
201        let result = from_test_data(LuaVersion::Lua51, &[0, 1, 4, 8, 6]);
202        assert_eq!(result, Err(LunifyError::UnsupportedInstructionWidth(6)));
203    }
204
205    #[test]
206    fn unsupported_number_width() {
207        let result = from_test_data(LuaVersion::Lua51, &[0, 1, 4, 8, 4, 6]);
208        assert_eq!(result, Err(LunifyError::UnsupportedNumberWidth(6)));
209    }
210}