#![doc = include_str!("readme.md")]
use crate::{
formats::luac::LuacReadConfig,
program::{LuaProgram, LuaVersion, LuacCodeObject, LuacHeader},
};
use gaia_binary::{BinaryReader, Fixed, LittleEndian};
use gaia_types::{GaiaDiagnostics, GaiaError};
use std::{
cell::RefCell,
io::{Read, Seek},
sync::OnceLock,
};
#[derive(Debug, Clone, Copy)]
pub struct LuacInfo {
pub header: LuacHeader,
pub version: LuaVersion,
}
#[derive(Debug)]
pub struct LuacReader<'config, R: std::io::Read> {
config: &'config LuacReadConfig,
reader: RefCell<BinaryReader<R, Fixed<LittleEndian>>>,
info: OnceLock<LuacInfo>,
program: OnceLock<LuaProgram>,
}
impl LuacReadConfig {
pub fn as_reader<R: Read + Seek>(&self, reader: R) -> LuacReader<'_, R> {
LuacReader::new(reader, self)
}
}
impl<'config, R: std::io::Read> LuacReader<'config, R> {
pub fn new(reader: R, config: &'config LuacReadConfig) -> Self {
Self {
config,
reader: RefCell::new(BinaryReader::<R, Fixed<LittleEndian>>::new(reader)),
info: Default::default(),
program: Default::default(),
}
}
pub fn finish(self) -> GaiaDiagnostics<LuaProgram>
where
R: std::io::Read + std::io::Seek,
{
match self.get_program() {
Ok(program) => {
let errors = self.reader.borrow_mut().take_errors();
GaiaDiagnostics { result: Ok(program.clone()), diagnostics: errors }
}
Err(e) => {
let errors = self.reader.borrow_mut().take_errors();
GaiaDiagnostics { result: Err(e), diagnostics: errors }
}
}
}
}
impl<'config, R: std::io::Read + std::io::Seek> LuacReader<'config, R> {
pub fn get_program(&self) -> Result<&LuaProgram, GaiaError> {
match self.program.get() {
Some(program) => Ok(program),
None => {
let program = self.read_program()?;
Ok(self.program.get_or_init(|| program))
}
}
}
pub fn get_info(&self) -> Result<&LuacInfo, GaiaError> {
match self.info.get() {
Some(info) => Ok(info),
None => {
let info = self.read_info()?;
Ok(self.info.get_or_init(|| info))
}
}
}
fn read_info(&self) -> Result<LuacInfo, GaiaError> {
let mut reader = self.reader.borrow_mut();
reader.seek(std::io::SeekFrom::Start(0))?;
let current_pos = reader.stream_position()?;
reader.seek(std::io::SeekFrom::End(0))?;
let file_size = reader.stream_position()?;
reader.seek(std::io::SeekFrom::Start(current_pos))?;
if file_size == 0 {
return Err(GaiaError::custom_error("File is empty".to_string()));
}
let header = self.read_header(&mut reader)?;
let version = if self.config.version != LuaVersion::Unknown {
self.config.version
}
else {
LuaVersion::from_byte(header.version.to_byte())
};
Ok(LuacInfo { header, version })
}
fn read_program(&self) -> Result<LuaProgram, GaiaError> {
let mut reader = self.reader.borrow_mut();
reader.seek(std::io::SeekFrom::Start(0))?;
let header = self.read_header(&mut reader)?;
let version = if self.config.version != LuaVersion::Unknown {
self.config.version
}
else {
LuaVersion::from_byte(header.version.to_byte())
};
let code_object = self.read_code_object(&mut reader)?;
let program = LuaProgram { header, code_object };
Ok(program)
}
fn read_header(&self, reader: &mut BinaryReader<R, Fixed<LittleEndian>>) -> Result<LuacHeader, GaiaError> {
let mut magic = [0u8; 4];
reader.read_exact(&mut magic).map_err(|e| GaiaError::custom_error(format!("Failed to read magic bytes: {}", e)))?;
if self.config.check_magic_head {
let expected_magic = [0x1b, b'L', b'u', b'a'];
if magic != expected_magic {
return Err(GaiaError::custom_error(format!(
"Invalid Lua bytecode magic: expected {:?}, got {:?}",
expected_magic, magic
)));
}
}
let version_byte =
reader.read_u8().map_err(|e| GaiaError::custom_error(format!("Failed to read version byte: {}", e)))?;
let version = LuaVersion::from_byte(version_byte);
let format_version =
reader.read_u8().map_err(|e| GaiaError::custom_error(format!("Failed to read format version: {}", e)))?;
let endianness = reader.read_u8().map_err(|e| GaiaError::custom_error(format!("Failed to read endianness: {}", e)))?;
let int_size = reader.read_u8().map_err(|e| GaiaError::custom_error(format!("Failed to read int_size: {}", e)))?;
let size_t_size =
reader.read_u8().map_err(|e| GaiaError::custom_error(format!("Failed to read size_t_size: {}", e)))?;
let instruction_size =
reader.read_u8().map_err(|e| GaiaError::custom_error(format!("Failed to read instruction_size: {}", e)))?;
let lua_number_size =
reader.read_u8().map_err(|e| GaiaError::custom_error(format!("Failed to read lua_number_size: {}", e)))?;
let integral_flag =
reader.read_u8().map_err(|e| GaiaError::custom_error(format!("Failed to read integral_flag: {}", e)))?;
Ok(LuacHeader {
magic,
version,
format_version,
endianness,
int_size,
size_t_size,
instruction_size,
lua_number_size,
integral_flag,
flags: 0,
timestamp: None,
size: None,
hash: None,
})
}
fn read_code_object(&self, _reader: &mut BinaryReader<R, Fixed<LittleEndian>>) -> Result<LuacCodeObject, GaiaError> {
Ok(LuacCodeObject {
source_name: String::new(),
first_line: 0,
last_line: 0,
num_params: 0,
is_vararg: 0,
max_stack_size: 0,
nested_functions: Vec::new(),
upvalues: Vec::new(),
local_vars: Vec::new(),
line_info: Vec::new(),
co_argcount: 0,
co_nlocal: 0,
co_stacks: 0,
num_upval: 0,
co_code: Vec::new(),
co_consts: Vec::new(),
upvalue_n: 0,
})
}
}
impl Default for LuaProgram {
fn default() -> Self {
Self { header: LuacHeader::default(), code_object: LuacCodeObject::default() }
}
}
impl Default for LuacHeader {
fn default() -> Self {
Self {
magic: [0x1b, b'L', b'u', b'a'],
version: LuaVersion::Unknown,
format_version: 0,
endianness: 1, int_size: 4,
size_t_size: 8,
instruction_size: 4,
lua_number_size: 8,
integral_flag: 0,
flags: 0,
timestamp: None,
size: None,
hash: None,
}
}
}
impl Default for LuacCodeObject {
fn default() -> Self {
Self {
source_name: String::new(),
first_line: 0,
last_line: 0,
num_params: 0,
is_vararg: 0,
max_stack_size: 0,
nested_functions: Vec::new(),
upvalues: Vec::new(),
local_vars: Vec::new(),
line_info: Vec::new(),
co_argcount: 0,
co_nlocal: 0,
co_stacks: 0,
num_upval: 0,
co_code: Vec::new(),
co_consts: Vec::new(),
upvalue_n: 0,
}
}
}