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
228fn 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
253fn 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
274fn 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
299fn 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
329fn 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
368fn 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
382fn 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
393fn 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
404fn 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
421fn 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
439fn 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
460fn 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
484fn 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
492fn 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
518fn 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
540fn 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
584fn 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 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;