use luau_common::flags;
use crate::native::{NativeCallContext, NativeCallResult, NativeFunction};
use crate::thread::Thread;
static BUFFER_LIB: [NativeFunction; 28] = [
NativeFunction {
name: "create",
function: buffer_create,
},
NativeFunction {
name: "fromstring",
function: buffer_from_string,
},
NativeFunction {
name: "tostring",
function: buffer_to_string,
},
NativeFunction {
name: "readi8",
function: buffer_read_integer::<i8>,
},
NativeFunction {
name: "readu8",
function: buffer_read_integer::<u8>,
},
NativeFunction {
name: "readi16",
function: buffer_read_integer::<i16>,
},
NativeFunction {
name: "readu16",
function: buffer_read_integer::<u16>,
},
NativeFunction {
name: "readi32",
function: buffer_read_integer::<i32>,
},
NativeFunction {
name: "readu32",
function: buffer_read_integer::<u32>,
},
NativeFunction {
name: "readf32",
function: buffer_read_fp::<f32>,
},
NativeFunction {
name: "readf64",
function: buffer_read_fp::<f64>,
},
NativeFunction {
name: "writei8",
function: buffer_write_integer::<i8>,
},
NativeFunction {
name: "writeu8",
function: buffer_write_integer::<u8>,
},
NativeFunction {
name: "writei16",
function: buffer_write_integer::<i16>,
},
NativeFunction {
name: "writeu16",
function: buffer_write_integer::<u16>,
},
NativeFunction {
name: "writei32",
function: buffer_write_integer::<i32>,
},
NativeFunction {
name: "writeu32",
function: buffer_write_integer::<u32>,
},
NativeFunction {
name: "writef32",
function: buffer_write_fp::<f32>,
},
NativeFunction {
name: "writef64",
function: buffer_write_fp::<f64>,
},
NativeFunction {
name: "readstring",
function: buffer_read_string,
},
NativeFunction {
name: "writestring",
function: buffer_write_string,
},
NativeFunction {
name: "len",
function: buffer_len,
},
NativeFunction {
name: "copy",
function: buffer_copy,
},
NativeFunction {
name: "fill",
function: buffer_fill,
},
NativeFunction {
name: "readbits",
function: buffer_read_bits,
},
NativeFunction {
name: "writebits",
function: buffer_write_bits,
},
NativeFunction {
name: "readinteger",
function: buffer_read_long,
},
NativeFunction {
name: "writeinteger",
function: buffer_write_long,
},
];
static BUFFER_LIB_NOINTEGER: [NativeFunction; 26] = [
NativeFunction {
name: "create",
function: buffer_create,
},
NativeFunction {
name: "fromstring",
function: buffer_from_string,
},
NativeFunction {
name: "tostring",
function: buffer_to_string,
},
NativeFunction {
name: "readi8",
function: buffer_read_integer::<i8>,
},
NativeFunction {
name: "readu8",
function: buffer_read_integer::<u8>,
},
NativeFunction {
name: "readi16",
function: buffer_read_integer::<i16>,
},
NativeFunction {
name: "readu16",
function: buffer_read_integer::<u16>,
},
NativeFunction {
name: "readi32",
function: buffer_read_integer::<i32>,
},
NativeFunction {
name: "readu32",
function: buffer_read_integer::<u32>,
},
NativeFunction {
name: "readf32",
function: buffer_read_fp::<f32>,
},
NativeFunction {
name: "readf64",
function: buffer_read_fp::<f64>,
},
NativeFunction {
name: "writei8",
function: buffer_write_integer::<i8>,
},
NativeFunction {
name: "writeu8",
function: buffer_write_integer::<u8>,
},
NativeFunction {
name: "writei16",
function: buffer_write_integer::<i16>,
},
NativeFunction {
name: "writeu16",
function: buffer_write_integer::<u16>,
},
NativeFunction {
name: "writei32",
function: buffer_write_integer::<i32>,
},
NativeFunction {
name: "writeu32",
function: buffer_write_integer::<u32>,
},
NativeFunction {
name: "writef32",
function: buffer_write_fp::<f32>,
},
NativeFunction {
name: "writef64",
function: buffer_write_fp::<f64>,
},
NativeFunction {
name: "readstring",
function: buffer_read_string,
},
NativeFunction {
name: "writestring",
function: buffer_write_string,
},
NativeFunction {
name: "len",
function: buffer_len,
},
NativeFunction {
name: "copy",
function: buffer_copy,
},
NativeFunction {
name: "fill",
function: buffer_fill,
},
NativeFunction {
name: "readbits",
function: buffer_read_bits,
},
NativeFunction {
name: "writebits",
function: buffer_write_bits,
},
];
fn is_out_of_bounds(offset: i32, len: usize, access_size: u32) -> bool {
(u64::from(offset as u32)) + u64::from(access_size) > len as u64
}
unsafe fn read_le_value<T: Copy>(buffer: *mut u8, offset: usize) -> T {
let mut value = MaybeUninit::<T>::uninit();
unsafe {
std::ptr::copy_nonoverlapping(
buffer.add(offset),
value.as_mut_ptr().cast::<u8>(),
std::mem::size_of::<T>(),
);
}
#[cfg(target_endian = "big")]
unsafe {
std::slice::from_raw_parts_mut(value.as_mut_ptr().cast::<u8>(), std::mem::size_of::<T>())
.reverse();
}
unsafe { value.assume_init() }
}
fn buffer_read_integer<T>(ctx: NativeCallContext) -> NativeCallResult
where
T: Copy,
f64: From<T>,
{
let thread = ctx.raw_thread();
unsafe {
let (buffer, len) = thread.check_buffer(1)?;
let offset = thread.check_integer(2)?;
if is_out_of_bounds(offset, len, std::mem::size_of::<T>() as u32) {
return crate::error!(thread, "buffer access out of bounds").map_err(Into::into);
}
let value = read_le_value::<T>(buffer, offset as usize);
thread.push_number(f64::from(value))?;
Ok(1)
}
}
fn buffer_write_integer<T>(ctx: NativeCallContext) -> NativeCallResult
where
T: Copy,
{
let thread = ctx.raw_thread();
unsafe {
let (buffer, len) = thread.check_buffer(1)?;
let offset = thread.check_integer(2)?;
let value = thread.check_unsigned(3)?;
if is_out_of_bounds(offset, len, std::mem::size_of::<T>() as u32) {
return crate::error!(thread, "buffer access out of bounds").map_err(Into::into);
}
let bytes = value.to_le_bytes();
std::ptr::copy_nonoverlapping(
bytes.as_ptr(),
buffer.add(offset as usize),
std::mem::size_of::<T>(),
);
Ok(0)
}
}
fn buffer_read_fp<T>(ctx: NativeCallContext) -> NativeCallResult
where
T: Copy,
{
let thread = ctx.raw_thread();
unsafe {
let (buffer, len) = thread.check_buffer(1)?;
let offset = thread.check_integer(2)?;
if is_out_of_bounds(offset, len, std::mem::size_of::<T>() as u32) {
return crate::error!(thread, "buffer access out of bounds").map_err(Into::into);
}
match std::mem::size_of::<T>() {
4 => {
let value = f32::from_bits(read_le_value::<u32>(buffer, offset as usize));
thread.push_number(f64::from(value))?;
}
8 => {
let value = f64::from_bits(read_le_value::<u64>(buffer, offset as usize));
thread.push_number(value)?;
}
_ => unreachable!(),
}
Ok(1)
}
}
fn buffer_write_fp<T>(ctx: NativeCallContext) -> NativeCallResult
where
T: Copy,
{
let thread = ctx.raw_thread();
unsafe {
let (buffer, len) = thread.check_buffer(1)?;
let offset = thread.check_integer(2)?;
let value = thread.check_number(3)?;
if is_out_of_bounds(offset, len, std::mem::size_of::<T>() as u32) {
return crate::error!(thread, "buffer access out of bounds").map_err(Into::into);
}
match std::mem::size_of::<T>() {
4 => {
let bytes = (value as f32).to_bits().to_le_bytes();
std::ptr::copy_nonoverlapping(
bytes.as_ptr(),
buffer.add(offset as usize),
bytes.len(),
);
}
8 => {
let bytes = value.to_bits().to_le_bytes();
std::ptr::copy_nonoverlapping(
bytes.as_ptr(),
buffer.add(offset as usize),
bytes.len(),
);
}
_ => unreachable!(),
}
Ok(0)
}
}
fn buffer_create(ctx: NativeCallContext) -> NativeCallResult {
let thread = ctx.raw_thread();
unsafe {
let size = thread.check_integer(1)?;
if size < 0 {
return thread.lua_arg_error(1, "size").map_err(Into::into);
}
thread.new_buffer(size as usize)?;
Ok(1)
}
}
fn buffer_from_string(ctx: NativeCallContext) -> NativeCallResult {
let thread = ctx.raw_thread();
unsafe {
let value = thread.check_string(1)?;
let buffer = thread.new_buffer(value.len())?;
std::ptr::copy_nonoverlapping(value.as_ptr(), buffer, value.len());
Ok(1)
}
}
fn buffer_to_string(ctx: NativeCallContext) -> NativeCallResult {
let thread = ctx.raw_thread();
unsafe {
let (buffer, len) = thread.check_buffer(1)?;
let bytes = std::slice::from_raw_parts(buffer, len);
thread.push_string(bytes)?;
Ok(1)
}
}
fn buffer_read_long(ctx: NativeCallContext) -> NativeCallResult {
let thread = ctx.raw_thread();
unsafe {
let (buffer, len) = thread.check_buffer(1)?;
let offset = thread.check_integer(2)?;
if is_out_of_bounds(offset, len, std::mem::size_of::<i64>() as u32) {
return crate::error!(thread, "buffer access out of bounds").map_err(Into::into);
}
let value = read_le_value::<i64>(buffer, offset as usize);
thread.push_integer64(value)?;
Ok(1)
}
}
fn buffer_write_long(ctx: NativeCallContext) -> NativeCallResult {
let thread = ctx.raw_thread();
unsafe {
let (buffer, len) = thread.check_buffer(1)?;
let offset = thread.check_integer(2)?;
let value = thread.check_integer64(3)?;
if is_out_of_bounds(offset, len, std::mem::size_of::<i64>() as u32) {
return crate::error!(thread, "buffer access out of bounds").map_err(Into::into);
}
let bytes = value.to_le_bytes();
std::ptr::copy_nonoverlapping(bytes.as_ptr(), buffer.add(offset as usize), bytes.len());
Ok(0)
}
}
fn buffer_read_string(ctx: NativeCallContext) -> NativeCallResult {
let thread = ctx.raw_thread();
unsafe {
let (buffer, len) = thread.check_buffer(1)?;
let offset = thread.check_integer(2)?;
let size = thread.check_integer(3)?;
if size < 0 {
return thread.lua_arg_error(3, "size").map_err(Into::into);
}
if is_out_of_bounds(offset, len, size as u32) {
return crate::error!(thread, "buffer access out of bounds").map_err(Into::into);
}
let bytes = std::slice::from_raw_parts(buffer.add(offset as usize), size as usize);
thread.push_string(bytes)?;
}
Ok(1)
}
fn buffer_write_string(ctx: NativeCallContext) -> NativeCallResult {
let thread = ctx.raw_thread();
unsafe {
let (buffer, len) = thread.check_buffer(1)?;
let offset = thread.check_integer(2)?;
let value = thread.check_string(3)?;
let count = thread.opt_integer(4, value.len() as i32)?;
if count < 0 {
return thread.lua_arg_error(4, "count").map_err(Into::into);
}
if count as usize > value.len() {
return crate::error!(thread, "string length overflow").map_err(Into::into);
}
if is_out_of_bounds(offset, len, count as u32) {
return crate::error!(thread, "buffer access out of bounds").map_err(Into::into);
}
std::ptr::copy_nonoverlapping(value.as_ptr(), buffer.add(offset as usize), count as usize);
}
Ok(0)
}
fn buffer_len(ctx: NativeCallContext) -> NativeCallResult {
let thread = ctx.raw_thread();
let (_, len) = unsafe { thread.check_buffer(1)? };
unsafe { thread.push_number((len as u32) as f64)? };
Ok(1)
}
fn buffer_copy(ctx: NativeCallContext) -> NativeCallResult {
let thread = ctx.raw_thread();
unsafe {
let (target_buffer, target_len) = thread.check_buffer(1)?;
let target_offset = thread.check_integer(2)?;
let (source_buffer, source_len) = thread.check_buffer(3)?;
let source_offset = thread.opt_integer(4, 0)?;
let size = thread.opt_integer(5, source_len as i32 - source_offset)?;
if size < 0
|| is_out_of_bounds(source_offset, source_len, size as u32)
|| is_out_of_bounds(target_offset, target_len, size as u32)
{
return crate::error!(thread, "buffer access out of bounds").map_err(Into::into);
}
std::ptr::copy(
source_buffer.add(source_offset as usize),
target_buffer.add(target_offset as usize),
size as usize,
);
}
Ok(0)
}
fn buffer_fill(ctx: NativeCallContext) -> NativeCallResult {
let thread = ctx.raw_thread();
unsafe {
let (buffer, len) = thread.check_buffer(1)?;
let offset = thread.check_integer(2)?;
let value = thread.check_unsigned(3)?;
let size = thread.opt_integer(4, len as i32 - offset)?;
if size < 0 || is_out_of_bounds(offset, len, size as u32) {
return crate::error!(thread, "buffer access out of bounds").map_err(Into::into);
}
std::ptr::write_bytes(
buffer.add(offset as usize),
(value & 0xff) as u8,
size as usize,
)
};
Ok(0)
}
fn buffer_read_bits(ctx: NativeCallContext) -> NativeCallResult {
let thread = ctx.raw_thread();
unsafe {
let (buffer, len) = thread.check_buffer(1)?;
let bit_offset = thread.check_number(2)? as i64;
let bit_count = thread.check_integer(3)?;
if bit_offset < 0 {
return crate::error!(thread, "buffer access out of bounds").map_err(Into::into);
}
if (bit_count as u32) > 32 {
return crate::error!(thread, "bit count is out of range of [0; 32]")
.map_err(Into::into);
}
if bit_offset as u64 + bit_count as u64 > len as u64 * 8 {
return crate::error!(thread, "buffer access out of bounds").map_err(Into::into);
}
let start_byte = (bit_offset / 8) as usize;
let end_byte = ((bit_offset + bit_count as i64 + 7) / 8) as usize;
let mut data = 0u64;
#[cfg(target_endian = "big")]
{
for index in (start_byte..end_byte).rev() {
data = (data << 8) + *buffer.add(index) as u64;
}
}
#[cfg(not(target_endian = "big"))]
std::ptr::copy_nonoverlapping(
buffer.add(start_byte),
(&raw mut data).cast::<u8>(),
end_byte - start_byte,
);
let sub_byte_offset = (bit_offset & 0x7) as u64;
let mask = (1u64 << bit_count) - 1;
thread.push_unsigned(((data >> sub_byte_offset) & mask) as u32)?;
}
Ok(1)
}
fn buffer_write_bits(ctx: NativeCallContext) -> NativeCallResult {
let thread = ctx.raw_thread();
unsafe {
let (buffer, len) = thread.check_buffer(1)?;
let bit_offset = thread.check_number(2)? as i64;
let bit_count = thread.check_integer(3)?;
let value = thread.check_unsigned(4)?;
if bit_offset < 0 {
return crate::error!(thread, "buffer access out of bounds").map_err(Into::into);
}
if (bit_count as u32) > 32 {
return crate::error!(thread, "bit count is out of range of [0; 32]")
.map_err(Into::into);
}
if bit_offset as u64 + bit_count as u64 > len as u64 * 8 {
return crate::error!(thread, "buffer access out of bounds").map_err(Into::into);
}
let start_byte = (bit_offset / 8) as usize;
let end_byte = ((bit_offset + bit_count as i64 + 7) / 8) as usize;
let mut data = 0u64;
#[cfg(target_endian = "big")]
{
for index in (start_byte..end_byte).rev() {
data = data * 256 + *buffer.add(index) as u64;
}
}
#[cfg(not(target_endian = "big"))]
std::ptr::copy_nonoverlapping(
buffer.add(start_byte),
(&raw mut data).cast::<u8>(),
end_byte - start_byte,
);
let sub_byte_offset = (bit_offset & 0x7) as u64;
let mask = ((1u64 << bit_count) - 1) << sub_byte_offset;
data = (data & !mask) | (((value as u64) << sub_byte_offset) & mask);
#[cfg(target_endian = "big")]
{
for index in start_byte..end_byte {
*buffer.add(index) = (data & 0xff) as u8;
data >>= 8;
}
}
#[cfg(not(target_endian = "big"))]
std::ptr::copy_nonoverlapping(
(&raw const data).cast::<u8>(),
buffer.add(start_byte),
end_byte - start_byte,
);
}
Ok(0)
}
impl Thread {
pub unsafe fn open_buffer(&self) -> NativeCallResult {
let integer_library = flags::LuauIntegerLibrary.get();
if integer_library {
unsafe { self.register(Some(super::LUA_BUFFERLIB_NAME), &BUFFER_LIB[..])? };
} else {
unsafe { self.register(Some(super::LUA_BUFFERLIB_NAME), &BUFFER_LIB_NOINTEGER[..])? };
}
Ok(1)
}
}
use core::mem::MaybeUninit;