Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 11 additions & 8 deletions rbx_binary/src/chunk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::{
};

use crate::{
core::{RbxReadExt, RbxWriteExt},
core::{RbxReadExt, RbxWriteExt, RbxWriteInterleaved},
serializer::CompressionType,
};

Expand Down Expand Up @@ -78,13 +78,6 @@ impl ChunkBuilder {
}
}

/// Reserve bytes and use a closure to initialize them.
pub fn initialize_bytes_with(&mut self, len: usize, initialize_bytes: impl FnOnce(&mut [u8])) {
let current_len = self.buffer.len();
self.buffer.extend(core::iter::repeat_n(0, len));
initialize_bytes(&mut self.buffer[current_len..]);
}

/// Consume the chunk and write it to the given writer.
pub fn dump<W: Write>(self, mut writer: W) -> io::Result<()> {
writer.write_all(self.chunk_name)?;
Expand Down Expand Up @@ -133,6 +126,16 @@ impl Write for ChunkBuilder {
}
}

impl RbxWriteInterleaved for ChunkBuilder {
fn write_interleaved_bytes<const N: usize, I>(&mut self, values: I) -> io::Result<()>
where
I: IntoIterator<Item = [u8; N]>,
<I as IntoIterator>::IntoIter: ExactSizeIterator,
{
self.buffer.write_interleaved_bytes(values)
}
}

#[derive(Debug)]
struct ChunkHeader {
/// 4-byte short name for the chunk, like "INST" or "PRNT"
Expand Down
61 changes: 34 additions & 27 deletions rbx_binary/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ use rbx_reflection::{
ClassDescriptor, PropertyDescriptor, PropertyKind, PropertySerialization, ReflectionDatabase,
};

use crate::chunk::ChunkBuilder;

pub static FILE_MAGIC_HEADER: &[u8] = b"<roblox!";
pub static FILE_SIGNATURE: &[u8] = b"\x89\xff\x0d\x0a\x1a\x0a";
pub const FILE_VERSION: u16 = 0;
Expand Down Expand Up @@ -255,34 +253,17 @@ pub trait RbxWriteExt: Write {
}
}

impl ChunkBuilder {
pub trait RbxWriteInterleaved {
/// Takes `values` and writes it as a blob of data with each value
/// interleaved by `N` bytes.
pub fn write_interleaved_bytes<const N: usize, I>(&mut self, values: I) -> io::Result<()>
fn write_interleaved_bytes<const N: usize, I>(&mut self, values: I) -> io::Result<()>
where
I: IntoIterator<Item = [u8; N]>,
<I as IntoIterator>::IntoIter: ExactSizeIterator,
{
let values = values.into_iter();
let values_len = values.len();
let bytes_len = values_len * N;

let initialize_bytes = |buffer: &mut [u8]| {
for (i, bytes) in values.enumerate() {
for (b, byte) in IntoIterator::into_iter(bytes).enumerate() {
buffer[i + b * values_len] = byte;
}
}
};

self.initialize_bytes_with(bytes_len, initialize_bytes);

Ok(())
}
<I as IntoIterator>::IntoIter: ExactSizeIterator;

/// Writes all items from `values` into the buffer as a blob of interleaved
/// bytes. Transformation is applied to the values as they're written.
pub fn write_interleaved_i32_array<I>(&mut self, values: I) -> io::Result<()>
fn write_interleaved_i32_array<I>(&mut self, values: I) -> io::Result<()>
where
I: IntoIterator<Item = i32>,
<I as IntoIterator>::IntoIter: ExactSizeIterator,
Expand All @@ -292,7 +273,7 @@ impl ChunkBuilder {

/// Writes all items from `values` into the buffer as a blob of interleaved
/// bytes.
pub fn write_interleaved_u32_array<I>(&mut self, values: I) -> io::Result<()>
fn write_interleaved_u32_array<I>(&mut self, values: I) -> io::Result<()>
where
I: IntoIterator<Item = u32>,
<I as IntoIterator>::IntoIter: ExactSizeIterator,
Expand All @@ -302,7 +283,7 @@ impl ChunkBuilder {

/// Writes all items from `values` into the buffer as a blob of interleaved
/// bytes. Rotation is applied to the values as they're written.
pub fn write_interleaved_f32_array<I>(&mut self, values: I) -> io::Result<()>
fn write_interleaved_f32_array<I>(&mut self, values: I) -> io::Result<()>
where
I: IntoIterator<Item = f32>,
<I as IntoIterator>::IntoIter: ExactSizeIterator,
Expand All @@ -317,7 +298,7 @@ impl ChunkBuilder {
/// Writes all items from `values` into the buffer as a blob of interleaved
/// bytes. The appropriate transformation and de-accumulation is done as
/// values are written.
pub fn write_referent_array<I>(&mut self, values: I) -> io::Result<()>
fn write_referent_array<I>(&mut self, values: I) -> io::Result<()>
where
I: IntoIterator<Item = i32>,
<I as IntoIterator>::IntoIter: ExactSizeIterator,
Expand All @@ -334,7 +315,7 @@ impl ChunkBuilder {

/// Writes all items from `values` into the buffer as a blob of interleaved
/// bytes. Transformation is applied to the values as they're written.
pub fn write_interleaved_i64_array<I>(&mut self, values: I) -> io::Result<()>
fn write_interleaved_i64_array<I>(&mut self, values: I) -> io::Result<()>
where
I: IntoIterator<Item = i64>,
<I as IntoIterator>::IntoIter: ExactSizeIterator,
Expand All @@ -343,6 +324,32 @@ impl ChunkBuilder {
}
}

impl RbxWriteInterleaved for Vec<u8> {
fn write_interleaved_bytes<const N: usize, I>(&mut self, values: I) -> io::Result<()>
where
I: IntoIterator<Item = [u8; N]>,
<I as IntoIterator>::IntoIter: ExactSizeIterator,
{
let values = values.into_iter();
let values_len = values.len();
let bytes_len = values_len * N;

// Reserve space for new values
let current_len = self.len();
self.extend(core::iter::repeat_n(0, bytes_len));

// Write new values
let buffer = &mut self[current_len..];
for (i, bytes) in values.enumerate() {
for (b, byte) in IntoIterator::into_iter(bytes).enumerate() {
buffer[i + b * values_len] = byte;
}
}

Ok(())
}
}

impl<W> RbxWriteExt for W where W: Write {}

/// Applies the 'zigzag' transformation done by Roblox to many `i32` values.
Expand Down
6 changes: 3 additions & 3 deletions rbx_binary/src/serializer/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ use rbx_reflection::{
use crate::{
chunk::ChunkBuilder,
core::{
find_property_descriptors, PropertyDescriptors, RbxWriteExt, FILE_MAGIC_HEADER,
FILE_SIGNATURE, FILE_VERSION,
find_property_descriptors, PropertyDescriptors, RbxWriteExt, RbxWriteInterleaved,
FILE_MAGIC_HEADER, FILE_SIGNATURE, FILE_VERSION,
},
types::Type,
Serializer,
Expand Down Expand Up @@ -1468,7 +1468,7 @@ impl<'dom, 'db: 'dom, W: Write> SerializerState<'dom, 'db, W> {
}
}

chunk.write_interleaved_bytes::<16, _>(blobs)?;
chunk.write_interleaved_bytes(blobs)?;
}
Type::SecurityCapabilities => {
let mut capabilities = Vec::with_capacity(values.len());
Expand Down
101 changes: 37 additions & 64 deletions rbx_binary/src/tests/core_read_write.rs
Original file line number Diff line number Diff line change
@@ -1,77 +1,50 @@
use crate::{chunk::ChunkBuilder, core::RbxReadExt, CompressionType};
use crate::core::{RbxReadExt, RbxWriteInterleaved};

#[rustfmt::skip]
const BYTES_INTERLEAVED: &[u8] = &[
0, 0, 0,
1, 1, 1,
2, 2, 2,
3, 3, 3,
4, 4, 4,
5, 5, 5,
6, 6, 6,
7, 7, 7,
8, 8, 8,
9, 9, 9,
10, 10, 10,
11, 11, 11,
12, 12, 12,
13, 13, 13,
14, 14, 14,
15, 15, 15,
];

const BYTES_UNINTERLEAVED: [[u8; 16]; 3] = [
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
];

#[test]
fn read_interleaved_bytes() {
#[rustfmt::skip]
let mut input: &[u8] = &[
0, 0, 0,
1, 1, 1,
2, 2, 2,
3, 3, 3,
4, 4, 4,
5, 5, 5,
6, 6, 6,
7, 7, 7,
8, 8, 8,
9, 9, 9,
10, 10, 10,
11, 11, 11,
12, 12, 12,
13, 13, 13,
14, 14, 14,
15, 15, 15,
];
let mut input = BYTES_INTERLEAVED;
let expected = BYTES_UNINTERLEAVED;

let expected = &[
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
];
let mut array_iter = input.read_interleaved_bytes(expected.len()).unwrap();
let result = core::array::from_fn(|_| array_iter.next().unwrap());

let result: Vec<_> = input
.read_interleaved_bytes::<16>(expected.len())
.unwrap()
.collect();

assert_eq!(result, expected)
assert_eq!(result, expected);
assert!(array_iter.next().is_none());
}

#[test]
fn write_interleaved_bytes() {
let input: [[u8; 16]; 3] = [
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
];

#[rustfmt::skip]
let expected = &[
0, 0, 0,
1, 1, 1,
2, 2, 2,
3, 3, 3,
4, 4, 4,
5, 5, 5,
6, 6, 6,
7, 7, 7,
8, 8, 8,
9, 9, 9,
10, 10, 10,
11, 11, 11,
12, 12, 12,
13, 13, 13,
14, 14, 14,
15, 15, 15,
];

let mut chunk = ChunkBuilder::new(b"ASDF", CompressionType::None);
chunk.write_interleaved_bytes(input).unwrap();

let mut dump = Vec::new();
chunk.dump(&mut dump).unwrap();
let input = BYTES_UNINTERLEAVED;
let expected = BYTES_INTERLEAVED;

// the first 16 bytes are the chunk header
let result = &dump[16..];
let mut result = Vec::new();
result.write_interleaved_bytes(input).unwrap();

assert_eq!(result, expected);
}