#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
mod endianness;
mod version;
mod width;
pub use endianness::Endianness;
pub(crate) use version::LuaVersion;
pub use width::BitWidth;
use crate::serialization::{ByteStream, ByteWriter};
use crate::{LunifyError, Settings};
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Format {
pub format: u8,
pub endianness: Endianness,
pub integer_width: BitWidth,
pub size_t_width: BitWidth,
pub instruction_width: BitWidth,
pub number_width: BitWidth,
pub is_number_integral: bool,
}
impl Default for Format {
fn default() -> Self {
let size_t_width = match cfg!(target_pointer_width = "64") {
true => BitWidth::Bit64,
false => BitWidth::Bit32,
};
let endianness = match unsafe { *(&1u32 as *const u32 as *const u8) } {
0 => Endianness::Big,
1 => Endianness::Little,
_ => unreachable!(),
};
Self {
format: 0,
endianness,
integer_width: BitWidth::Bit32,
size_t_width,
instruction_width: BitWidth::Bit32,
number_width: BitWidth::Bit64,
is_number_integral: false,
}
}
}
impl Format {
pub(crate) fn from_byte_stream(byte_stream: &mut ByteStream, version: LuaVersion, settings: &Settings) -> Result<Self, LunifyError> {
let format = match version {
LuaVersion::Lua51 => byte_stream.byte()?,
LuaVersion::Lua50 => 0,
};
let endianness = byte_stream.byte()?.try_into()?;
let integer_width = byte_stream.byte()?.try_into().map_err(LunifyError::UnsupportedIntegerWidth)?;
let size_t_width = byte_stream.byte()?.try_into().map_err(LunifyError::UnsupportedSizeTWidth)?;
let instruction_width = byte_stream.byte()?.try_into().map_err(LunifyError::UnsupportedInstructionWidth)?;
if version == LuaVersion::Lua50 {
let instruction_format = byte_stream.slice(4)?;
let expected = [
settings.lua50.layout.opcode.size as u8,
settings.lua50.layout.a.size as u8,
settings.lua50.layout.b.size as u8,
settings.lua50.layout.c.size as u8,
];
if instruction_format != expected {
return Err(LunifyError::UnsupportedInstructionFormat(
instruction_format.try_into().unwrap(),
));
}
}
let number_width = byte_stream.byte()?.try_into().map_err(LunifyError::UnsupportedNumberWidth)?;
let is_number_integral = match version {
LuaVersion::Lua51 => byte_stream.byte()? == 1,
LuaVersion::Lua50 => {
let value = from_slice!(byte_stream, number_width, endianness, f32, f64);
value != 31415926.535897933
}
};
#[cfg(feature = "debug")]
{
println!("format: {format}");
println!("endianness: {endianness:?}");
println!("integer_width: {integer_width}");
println!("size_t_width: {size_t_width}");
println!("instruction_width: {instruction_width}");
println!("number_width: {number_width}");
println!("is_number_integral: {is_number_integral}");
}
Ok(Self {
format,
endianness,
integer_width,
size_t_width,
instruction_width,
number_width,
is_number_integral,
})
}
pub(crate) fn write(&self, byte_writer: &mut ByteWriter) {
byte_writer.byte(self.format);
byte_writer.byte(self.endianness.into());
byte_writer.byte(self.integer_width.into());
byte_writer.byte(self.size_t_width.into());
byte_writer.byte(self.instruction_width.into());
byte_writer.byte(self.number_width.into());
byte_writer.byte(self.is_number_integral as u8);
}
}
#[cfg(test)]
mod tests {
use super::LuaVersion;
use crate::serialization::{ByteStream, ByteWriter};
use crate::{BitWidth, Endianness, Format, LunifyError, Settings};
const EXPECTED_FORMAT: Format = Format {
format: 0,
endianness: Endianness::Little,
integer_width: BitWidth::Bit32,
size_t_width: BitWidth::Bit64,
instruction_width: BitWidth::Bit32,
number_width: BitWidth::Bit64,
is_number_integral: false,
};
fn from_test_data(version: LuaVersion, bytes: &[u8]) -> Result<Format, LunifyError> {
let mut byte_stream = ByteStream::new(bytes);
let result = Format::from_byte_stream(&mut byte_stream, version, &Settings::default());
assert!(byte_stream.is_empty());
result
}
#[test]
fn lua_51() -> Result<(), LunifyError> {
let result = from_test_data(LuaVersion::Lua51, &[0, 1, 4, 8, 4, 8, 0])?;
assert_eq!(result, EXPECTED_FORMAT);
Ok(())
}
#[test]
fn lua50() -> Result<(), LunifyError> {
let result = from_test_data(LuaVersion::Lua50, &[
1, 4, 8, 4, 6, 8, 9, 9, 8, 0xB6, 0x09, 0x93, 0x68, 0xE7, 0xF5, 0x7D, 0x41,
])?;
assert_eq!(result, EXPECTED_FORMAT);
Ok(())
}
#[test]
fn write() {
let mut byter_writer = ByteWriter::new(&EXPECTED_FORMAT);
EXPECTED_FORMAT.write(&mut byter_writer);
assert_eq!(byter_writer.finalize(), [0, 1, 4, 8, 4, 8, 0]);
}
#[test]
fn lua50_unsupported_instruction_format() {
let result = from_test_data(LuaVersion::Lua50, &[1, 4, 8, 4, 6, 9, 8, 9]);
assert_eq!(result, Err(LunifyError::UnsupportedInstructionFormat([6, 9, 8, 9])));
}
#[test]
fn unsupported_integer_width() {
let result = from_test_data(LuaVersion::Lua51, &[0, 1, 6]);
assert_eq!(result, Err(LunifyError::UnsupportedIntegerWidth(6)));
}
#[test]
fn unsupported_size_t_width() {
let result = from_test_data(LuaVersion::Lua51, &[0, 1, 4, 6]);
assert_eq!(result, Err(LunifyError::UnsupportedSizeTWidth(6)));
}
#[test]
fn unsupported_instruction_width() {
let result = from_test_data(LuaVersion::Lua51, &[0, 1, 4, 8, 6]);
assert_eq!(result, Err(LunifyError::UnsupportedInstructionWidth(6)));
}
#[test]
fn unsupported_number_width() {
let result = from_test_data(LuaVersion::Lua51, &[0, 1, 4, 8, 4, 6]);
assert_eq!(result, Err(LunifyError::UnsupportedNumberWidth(6)));
}
}