Skip to main content

luau_vm/libs/
buffer.rs

1use luau_common::flags;
2
3use crate::native::{NativeCallContext, NativeCallResult, NativeFunction};
4use crate::thread::Thread;
5
6static BUFFER_LIB: [NativeFunction; 28] = [
7    NativeFunction {
8        name: "create",
9        function: buffer_create,
10    },
11    NativeFunction {
12        name: "fromstring",
13        function: buffer_from_string,
14    },
15    NativeFunction {
16        name: "tostring",
17        function: buffer_to_string,
18    },
19    NativeFunction {
20        name: "readi8",
21        function: buffer_read_integer::<i8>,
22    },
23    NativeFunction {
24        name: "readu8",
25        function: buffer_read_integer::<u8>,
26    },
27    NativeFunction {
28        name: "readi16",
29        function: buffer_read_integer::<i16>,
30    },
31    NativeFunction {
32        name: "readu16",
33        function: buffer_read_integer::<u16>,
34    },
35    NativeFunction {
36        name: "readi32",
37        function: buffer_read_integer::<i32>,
38    },
39    NativeFunction {
40        name: "readu32",
41        function: buffer_read_integer::<u32>,
42    },
43    NativeFunction {
44        name: "readf32",
45        function: buffer_read_fp::<f32>,
46    },
47    NativeFunction {
48        name: "readf64",
49        function: buffer_read_fp::<f64>,
50    },
51    NativeFunction {
52        name: "writei8",
53        function: buffer_write_integer::<i8>,
54    },
55    NativeFunction {
56        name: "writeu8",
57        function: buffer_write_integer::<u8>,
58    },
59    NativeFunction {
60        name: "writei16",
61        function: buffer_write_integer::<i16>,
62    },
63    NativeFunction {
64        name: "writeu16",
65        function: buffer_write_integer::<u16>,
66    },
67    NativeFunction {
68        name: "writei32",
69        function: buffer_write_integer::<i32>,
70    },
71    NativeFunction {
72        name: "writeu32",
73        function: buffer_write_integer::<u32>,
74    },
75    NativeFunction {
76        name: "writef32",
77        function: buffer_write_fp::<f32>,
78    },
79    NativeFunction {
80        name: "writef64",
81        function: buffer_write_fp::<f64>,
82    },
83    NativeFunction {
84        name: "readstring",
85        function: buffer_read_string,
86    },
87    NativeFunction {
88        name: "writestring",
89        function: buffer_write_string,
90    },
91    NativeFunction {
92        name: "len",
93        function: buffer_len,
94    },
95    NativeFunction {
96        name: "copy",
97        function: buffer_copy,
98    },
99    NativeFunction {
100        name: "fill",
101        function: buffer_fill,
102    },
103    NativeFunction {
104        name: "readbits",
105        function: buffer_read_bits,
106    },
107    NativeFunction {
108        name: "writebits",
109        function: buffer_write_bits,
110    },
111    NativeFunction {
112        name: "readinteger",
113        function: buffer_read_long,
114    },
115    NativeFunction {
116        name: "writeinteger",
117        function: buffer_write_long,
118    },
119];
120
121static BUFFER_LIB_NOINTEGER: [NativeFunction; 26] = [
122    NativeFunction {
123        name: "create",
124        function: buffer_create,
125    },
126    NativeFunction {
127        name: "fromstring",
128        function: buffer_from_string,
129    },
130    NativeFunction {
131        name: "tostring",
132        function: buffer_to_string,
133    },
134    NativeFunction {
135        name: "readi8",
136        function: buffer_read_integer::<i8>,
137    },
138    NativeFunction {
139        name: "readu8",
140        function: buffer_read_integer::<u8>,
141    },
142    NativeFunction {
143        name: "readi16",
144        function: buffer_read_integer::<i16>,
145    },
146    NativeFunction {
147        name: "readu16",
148        function: buffer_read_integer::<u16>,
149    },
150    NativeFunction {
151        name: "readi32",
152        function: buffer_read_integer::<i32>,
153    },
154    NativeFunction {
155        name: "readu32",
156        function: buffer_read_integer::<u32>,
157    },
158    NativeFunction {
159        name: "readf32",
160        function: buffer_read_fp::<f32>,
161    },
162    NativeFunction {
163        name: "readf64",
164        function: buffer_read_fp::<f64>,
165    },
166    NativeFunction {
167        name: "writei8",
168        function: buffer_write_integer::<i8>,
169    },
170    NativeFunction {
171        name: "writeu8",
172        function: buffer_write_integer::<u8>,
173    },
174    NativeFunction {
175        name: "writei16",
176        function: buffer_write_integer::<i16>,
177    },
178    NativeFunction {
179        name: "writeu16",
180        function: buffer_write_integer::<u16>,
181    },
182    NativeFunction {
183        name: "writei32",
184        function: buffer_write_integer::<i32>,
185    },
186    NativeFunction {
187        name: "writeu32",
188        function: buffer_write_integer::<u32>,
189    },
190    NativeFunction {
191        name: "writef32",
192        function: buffer_write_fp::<f32>,
193    },
194    NativeFunction {
195        name: "writef64",
196        function: buffer_write_fp::<f64>,
197    },
198    NativeFunction {
199        name: "readstring",
200        function: buffer_read_string,
201    },
202    NativeFunction {
203        name: "writestring",
204        function: buffer_write_string,
205    },
206    NativeFunction {
207        name: "len",
208        function: buffer_len,
209    },
210    NativeFunction {
211        name: "copy",
212        function: buffer_copy,
213    },
214    NativeFunction {
215        name: "fill",
216        function: buffer_fill,
217    },
218    NativeFunction {
219        name: "readbits",
220        function: buffer_read_bits,
221    },
222    NativeFunction {
223        name: "writebits",
224        function: buffer_write_bits,
225    },
226];
227
228/// `isoutofbounds`
229fn is_out_of_bounds(offset: i32, len: usize, access_size: u32) -> bool {
230    (u64::from(offset as u32)) + u64::from(access_size) > len as u64
231}
232
233unsafe fn read_le_value<T: Copy>(buffer: *mut u8, offset: usize) -> T {
234    let mut value = MaybeUninit::<T>::uninit();
235
236    unsafe {
237        std::ptr::copy_nonoverlapping(
238            buffer.add(offset),
239            value.as_mut_ptr().cast::<u8>(),
240            std::mem::size_of::<T>(),
241        );
242    }
243
244    #[cfg(target_endian = "big")]
245    unsafe {
246        std::slice::from_raw_parts_mut(value.as_mut_ptr().cast::<u8>(), std::mem::size_of::<T>())
247            .reverse();
248    }
249
250    unsafe { value.assume_init() }
251}
252
253/// `buffer_readinteger<T>`
254fn buffer_read_integer<T>(ctx: NativeCallContext) -> NativeCallResult
255where
256    T: Copy,
257    f64: From<T>,
258{
259    let thread = ctx.raw_thread();
260    unsafe {
261        let (buffer, len) = thread.check_buffer(1)?;
262        let offset = thread.check_integer(2)?;
263
264        if is_out_of_bounds(offset, len, std::mem::size_of::<T>() as u32) {
265            return crate::error!(thread, "buffer access out of bounds").map_err(Into::into);
266        }
267
268        let value = read_le_value::<T>(buffer, offset as usize);
269        thread.push_number(f64::from(value))?;
270        Ok(1)
271    }
272}
273
274/// `buffer_writeinteger<T>`
275fn buffer_write_integer<T>(ctx: NativeCallContext) -> NativeCallResult
276where
277    T: Copy,
278{
279    let thread = ctx.raw_thread();
280    unsafe {
281        let (buffer, len) = thread.check_buffer(1)?;
282        let offset = thread.check_integer(2)?;
283        let value = thread.check_unsigned(3)?;
284
285        if is_out_of_bounds(offset, len, std::mem::size_of::<T>() as u32) {
286            return crate::error!(thread, "buffer access out of bounds").map_err(Into::into);
287        }
288
289        let bytes = value.to_le_bytes();
290        std::ptr::copy_nonoverlapping(
291            bytes.as_ptr(),
292            buffer.add(offset as usize),
293            std::mem::size_of::<T>(),
294        );
295        Ok(0)
296    }
297}
298
299/// `buffer_readfp<T, StorageType>`
300fn buffer_read_fp<T>(ctx: NativeCallContext) -> NativeCallResult
301where
302    T: Copy,
303{
304    let thread = ctx.raw_thread();
305    unsafe {
306        let (buffer, len) = thread.check_buffer(1)?;
307        let offset = thread.check_integer(2)?;
308
309        if is_out_of_bounds(offset, len, std::mem::size_of::<T>() as u32) {
310            return crate::error!(thread, "buffer access out of bounds").map_err(Into::into);
311        }
312
313        match std::mem::size_of::<T>() {
314            4 => {
315                let value = f32::from_bits(read_le_value::<u32>(buffer, offset as usize));
316                thread.push_number(f64::from(value))?;
317            }
318            8 => {
319                let value = f64::from_bits(read_le_value::<u64>(buffer, offset as usize));
320                thread.push_number(value)?;
321            }
322            _ => unreachable!(),
323        }
324
325        Ok(1)
326    }
327}
328
329/// `buffer_writefp<T, StorageType>`
330fn buffer_write_fp<T>(ctx: NativeCallContext) -> NativeCallResult
331where
332    T: Copy,
333{
334    let thread = ctx.raw_thread();
335    unsafe {
336        let (buffer, len) = thread.check_buffer(1)?;
337        let offset = thread.check_integer(2)?;
338        let value = thread.check_number(3)?;
339
340        if is_out_of_bounds(offset, len, std::mem::size_of::<T>() as u32) {
341            return crate::error!(thread, "buffer access out of bounds").map_err(Into::into);
342        }
343
344        match std::mem::size_of::<T>() {
345            4 => {
346                let bytes = (value as f32).to_bits().to_le_bytes();
347                std::ptr::copy_nonoverlapping(
348                    bytes.as_ptr(),
349                    buffer.add(offset as usize),
350                    bytes.len(),
351                );
352            }
353            8 => {
354                let bytes = value.to_bits().to_le_bytes();
355                std::ptr::copy_nonoverlapping(
356                    bytes.as_ptr(),
357                    buffer.add(offset as usize),
358                    bytes.len(),
359                );
360            }
361            _ => unreachable!(),
362        }
363
364        Ok(0)
365    }
366}
367
368/// `buffer_create`
369fn buffer_create(ctx: NativeCallContext) -> NativeCallResult {
370    let thread = ctx.raw_thread();
371    unsafe {
372        let size = thread.check_integer(1)?;
373        if size < 0 {
374            return thread.lua_arg_error(1, "size").map_err(Into::into);
375        }
376
377        thread.new_buffer(size as usize)?;
378        Ok(1)
379    }
380}
381
382/// `buffer_fromstring`
383fn buffer_from_string(ctx: NativeCallContext) -> NativeCallResult {
384    let thread = ctx.raw_thread();
385    unsafe {
386        let value = thread.check_string(1)?;
387        let buffer = thread.new_buffer(value.len())?;
388        std::ptr::copy_nonoverlapping(value.as_ptr(), buffer, value.len());
389        Ok(1)
390    }
391}
392
393/// `buffer_tostring`
394fn buffer_to_string(ctx: NativeCallContext) -> NativeCallResult {
395    let thread = ctx.raw_thread();
396    unsafe {
397        let (buffer, len) = thread.check_buffer(1)?;
398        let bytes = std::slice::from_raw_parts(buffer, len);
399        thread.push_string(bytes)?;
400        Ok(1)
401    }
402}
403
404/// `buffer_readlong`
405fn buffer_read_long(ctx: NativeCallContext) -> NativeCallResult {
406    let thread = ctx.raw_thread();
407    unsafe {
408        let (buffer, len) = thread.check_buffer(1)?;
409        let offset = thread.check_integer(2)?;
410
411        if is_out_of_bounds(offset, len, std::mem::size_of::<i64>() as u32) {
412            return crate::error!(thread, "buffer access out of bounds").map_err(Into::into);
413        }
414
415        let value = read_le_value::<i64>(buffer, offset as usize);
416        thread.push_integer64(value)?;
417        Ok(1)
418    }
419}
420
421/// `buffer_writelong`
422fn buffer_write_long(ctx: NativeCallContext) -> NativeCallResult {
423    let thread = ctx.raw_thread();
424    unsafe {
425        let (buffer, len) = thread.check_buffer(1)?;
426        let offset = thread.check_integer(2)?;
427        let value = thread.check_integer64(3)?;
428
429        if is_out_of_bounds(offset, len, std::mem::size_of::<i64>() as u32) {
430            return crate::error!(thread, "buffer access out of bounds").map_err(Into::into);
431        }
432
433        let bytes = value.to_le_bytes();
434        std::ptr::copy_nonoverlapping(bytes.as_ptr(), buffer.add(offset as usize), bytes.len());
435        Ok(0)
436    }
437}
438
439/// `buffer_readstring`
440fn buffer_read_string(ctx: NativeCallContext) -> NativeCallResult {
441    let thread = ctx.raw_thread();
442    unsafe {
443        let (buffer, len) = thread.check_buffer(1)?;
444        let offset = thread.check_integer(2)?;
445        let size = thread.check_integer(3)?;
446
447        if size < 0 {
448            return thread.lua_arg_error(3, "size").map_err(Into::into);
449        }
450        if is_out_of_bounds(offset, len, size as u32) {
451            return crate::error!(thread, "buffer access out of bounds").map_err(Into::into);
452        }
453
454        let bytes = std::slice::from_raw_parts(buffer.add(offset as usize), size as usize);
455        thread.push_string(bytes)?;
456    }
457    Ok(1)
458}
459
460/// `buffer_writestring`
461fn buffer_write_string(ctx: NativeCallContext) -> NativeCallResult {
462    let thread = ctx.raw_thread();
463    unsafe {
464        let (buffer, len) = thread.check_buffer(1)?;
465        let offset = thread.check_integer(2)?;
466        let value = thread.check_string(3)?;
467        let count = thread.opt_integer(4, value.len() as i32)?;
468
469        if count < 0 {
470            return thread.lua_arg_error(4, "count").map_err(Into::into);
471        }
472        if count as usize > value.len() {
473            return crate::error!(thread, "string length overflow").map_err(Into::into);
474        }
475        if is_out_of_bounds(offset, len, count as u32) {
476            return crate::error!(thread, "buffer access out of bounds").map_err(Into::into);
477        }
478
479        std::ptr::copy_nonoverlapping(value.as_ptr(), buffer.add(offset as usize), count as usize);
480    }
481    Ok(0)
482}
483
484/// `buffer_len`
485fn buffer_len(ctx: NativeCallContext) -> NativeCallResult {
486    let thread = ctx.raw_thread();
487    let (_, len) = unsafe { thread.check_buffer(1)? };
488    unsafe { thread.push_number((len as u32) as f64)? };
489    Ok(1)
490}
491
492/// `buffer_copy`
493fn buffer_copy(ctx: NativeCallContext) -> NativeCallResult {
494    let thread = ctx.raw_thread();
495    unsafe {
496        let (target_buffer, target_len) = thread.check_buffer(1)?;
497        let target_offset = thread.check_integer(2)?;
498        let (source_buffer, source_len) = thread.check_buffer(3)?;
499        let source_offset = thread.opt_integer(4, 0)?;
500        let size = thread.opt_integer(5, source_len as i32 - source_offset)?;
501
502        if size < 0
503            || is_out_of_bounds(source_offset, source_len, size as u32)
504            || is_out_of_bounds(target_offset, target_len, size as u32)
505        {
506            return crate::error!(thread, "buffer access out of bounds").map_err(Into::into);
507        }
508
509        std::ptr::copy(
510            source_buffer.add(source_offset as usize),
511            target_buffer.add(target_offset as usize),
512            size as usize,
513        );
514    }
515    Ok(0)
516}
517
518/// `buffer_fill`
519fn buffer_fill(ctx: NativeCallContext) -> NativeCallResult {
520    let thread = ctx.raw_thread();
521    unsafe {
522        let (buffer, len) = thread.check_buffer(1)?;
523        let offset = thread.check_integer(2)?;
524        let value = thread.check_unsigned(3)?;
525        let size = thread.opt_integer(4, len as i32 - offset)?;
526
527        if size < 0 || is_out_of_bounds(offset, len, size as u32) {
528            return crate::error!(thread, "buffer access out of bounds").map_err(Into::into);
529        }
530
531        std::ptr::write_bytes(
532            buffer.add(offset as usize),
533            (value & 0xff) as u8,
534            size as usize,
535        )
536    };
537    Ok(0)
538}
539
540/// `buffer_readbits`
541fn buffer_read_bits(ctx: NativeCallContext) -> NativeCallResult {
542    let thread = ctx.raw_thread();
543    unsafe {
544        let (buffer, len) = thread.check_buffer(1)?;
545        let bit_offset = thread.check_number(2)? as i64;
546        let bit_count = thread.check_integer(3)?;
547
548        if bit_offset < 0 {
549            return crate::error!(thread, "buffer access out of bounds").map_err(Into::into);
550        }
551        if (bit_count as u32) > 32 {
552            return crate::error!(thread, "bit count is out of range of [0; 32]")
553                .map_err(Into::into);
554        }
555        if bit_offset as u64 + bit_count as u64 > len as u64 * 8 {
556            return crate::error!(thread, "buffer access out of bounds").map_err(Into::into);
557        }
558
559        let start_byte = (bit_offset / 8) as usize;
560        let end_byte = ((bit_offset + bit_count as i64 + 7) / 8) as usize;
561        let mut data = 0u64;
562
563        #[cfg(target_endian = "big")]
564        {
565            for index in (start_byte..end_byte).rev() {
566                data = (data << 8) + *buffer.add(index) as u64;
567            }
568        }
569
570        #[cfg(not(target_endian = "big"))]
571        std::ptr::copy_nonoverlapping(
572            buffer.add(start_byte),
573            (&raw mut data).cast::<u8>(),
574            end_byte - start_byte,
575        );
576
577        let sub_byte_offset = (bit_offset & 0x7) as u64;
578        let mask = (1u64 << bit_count) - 1;
579        thread.push_unsigned(((data >> sub_byte_offset) & mask) as u32)?;
580    }
581    Ok(1)
582}
583
584/// `buffer_writebits`
585fn buffer_write_bits(ctx: NativeCallContext) -> NativeCallResult {
586    let thread = ctx.raw_thread();
587    unsafe {
588        let (buffer, len) = thread.check_buffer(1)?;
589        let bit_offset = thread.check_number(2)? as i64;
590        let bit_count = thread.check_integer(3)?;
591        let value = thread.check_unsigned(4)?;
592
593        if bit_offset < 0 {
594            return crate::error!(thread, "buffer access out of bounds").map_err(Into::into);
595        }
596        if (bit_count as u32) > 32 {
597            return crate::error!(thread, "bit count is out of range of [0; 32]")
598                .map_err(Into::into);
599        }
600        if bit_offset as u64 + bit_count as u64 > len as u64 * 8 {
601            return crate::error!(thread, "buffer access out of bounds").map_err(Into::into);
602        }
603
604        let start_byte = (bit_offset / 8) as usize;
605        let end_byte = ((bit_offset + bit_count as i64 + 7) / 8) as usize;
606        let mut data = 0u64;
607
608        #[cfg(target_endian = "big")]
609        {
610            for index in (start_byte..end_byte).rev() {
611                data = data * 256 + *buffer.add(index) as u64;
612            }
613        }
614
615        #[cfg(not(target_endian = "big"))]
616        std::ptr::copy_nonoverlapping(
617            buffer.add(start_byte),
618            (&raw mut data).cast::<u8>(),
619            end_byte - start_byte,
620        );
621
622        let sub_byte_offset = (bit_offset & 0x7) as u64;
623        let mask = ((1u64 << bit_count) - 1) << sub_byte_offset;
624        data = (data & !mask) | (((value as u64) << sub_byte_offset) & mask);
625
626        #[cfg(target_endian = "big")]
627        {
628            for index in start_byte..end_byte {
629                *buffer.add(index) = (data & 0xff) as u8;
630                data >>= 8;
631            }
632        }
633
634        #[cfg(not(target_endian = "big"))]
635        std::ptr::copy_nonoverlapping(
636            (&raw const data).cast::<u8>(),
637            buffer.add(start_byte),
638            end_byte - start_byte,
639        );
640    }
641    Ok(0)
642}
643
644impl Thread {
645    /// `luaopen_buffer`
646    pub unsafe fn open_buffer(&self) -> NativeCallResult {
647        let integer_library = flags::LuauIntegerLibrary.get();
648
649        if integer_library {
650            unsafe { self.register(Some(super::LUA_BUFFERLIB_NAME), &BUFFER_LIB[..])? };
651        } else {
652            unsafe { self.register(Some(super::LUA_BUFFERLIB_NAME), &BUFFER_LIB_NOINTEGER[..])? };
653        }
654
655        Ok(1)
656    }
657}
658use core::mem::MaybeUninit;