diff --git a/rbx_binary/src/core.rs b/rbx_binary/src/core.rs index 94f53146..07973bd1 100644 --- a/rbx_binary/src/core.rs +++ b/rbx_binary/src/core.rs @@ -315,6 +315,43 @@ pub trait RbxWriteExt: Write { impl RbxWriteExt for W where W: Write {} +/// Read a binary "string" in the format that Roblox's model files use. +/// +/// This function is safer than read_string because Roblox generally makes +/// no guarantees about encoding of things it calls strings. rbx_binary +/// makes a semantic differentiation between strings and binary buffers, +/// which makes it more strict than Roblox but more likely to be correct. +/// +/// This function is not part of RbxReadExt, and unlike +/// RbxReadExt::read_binary_string, this function only operates on a byte slice. +pub fn read_binary_string_slice<'a>(slice: &mut &'a [u8]) -> io::Result<&'a [u8]> { + let length = slice.read_le_u32()?; + + let out; + // split_at can panic if the slice is shorter than length + // but we do not expect that to happen. + (out, *slice) = slice.split_at(length as usize); + + Ok(out) +} + +/// Read a UTF-8 encoded string encoded how Roblox model files encode +/// strings. This function isn't always appropriate because Roblox's formats +/// generally aren't dilligent about data being valid Unicode. +/// +/// This function is not part of RbxReadExt, and unlike +/// RbxReadExt::read_string, this function only operates on a byte slice. +pub fn read_string_slice<'a>(slice: &mut &'a [u8]) -> io::Result<&'a str> { + let out = read_binary_string_slice(slice)?; + + core::str::from_utf8(out).map_err(|_| { + io::Error::new( + io::ErrorKind::InvalidData, + "stream did not contain valid UTF-8", + ) + }) +} + /// Applies the 'zigzag' transformation done by Roblox to many `i32` values. pub fn transform_i32(value: i32) -> i32 { (value << 1) ^ (value >> 31) diff --git a/rbx_binary/src/deserializer/state.rs b/rbx_binary/src/deserializer/state.rs index 4207f8b4..19155555 100644 --- a/rbx_binary/src/deserializer/state.rs +++ b/rbx_binary/src/deserializer/state.rs @@ -16,7 +16,7 @@ use rbx_reflection::{DataType, PropertyKind, PropertySerialization, ReflectionDa use crate::{ chunk::Chunk, - core::{find_property_descriptors, RbxReadExt}, + core::{find_property_descriptors, read_binary_string_slice, read_string_slice, RbxReadExt}, types::Type, }; @@ -276,7 +276,7 @@ impl<'db, R: Read> DeserializerState<'db, R> { #[profiling::function] pub(super) fn decode_inst_chunk(&mut self, mut chunk: &[u8]) -> Result<(), InnerError> { let type_id = chunk.read_le_u32()?; - let type_name = chunk.read_string()?; + let type_name = read_string_slice(&mut chunk)?; let object_format = chunk.read_u8()?; let number_instances = chunk.read_le_u32()?; @@ -295,7 +295,7 @@ impl<'db, R: Read> DeserializerState<'db, R> { .deserializer .database .classes - .get(type_name.as_str()) + .get(type_name) .map(|class| class.default_properties.len()) .unwrap_or(0); @@ -305,10 +305,7 @@ impl<'db, R: Read> DeserializerState<'db, R> { self.instances_by_ref.insert( referent, Instance { - builder: InstanceBuilder::with_property_capacity( - type_name.as_str(), - prop_capacity, - ), + builder: InstanceBuilder::with_property_capacity(type_name, prop_capacity), children: Vec::new(), }, ); @@ -329,7 +326,7 @@ impl<'db, R: Read> DeserializerState<'db, R> { #[profiling::function] pub(super) fn decode_prop_chunk(&mut self, mut chunk: &[u8]) -> Result<(), InnerError> { let type_id = chunk.read_le_u32()?; - let prop_name = chunk.read_string()?; + let prop_name = read_string_slice(&mut chunk)?; let type_info = self .type_infos @@ -382,8 +379,8 @@ impl<'db, R: Read> DeserializerState<'db, R> { for referent in &type_info.referents { let instance = self.instances_by_ref.get_mut(referent).unwrap(); - let binary_string = chunk.read_binary_string()?; - let value = match std::str::from_utf8(&binary_string) { + let binary_string = read_binary_string_slice(&mut chunk)?; + let value = match std::str::from_utf8(binary_string) { Ok(value) => Cow::Borrowed(value), Err(_) => { log::warn!( @@ -393,7 +390,7 @@ This may cause unexpected or broken behavior in your final results if you rely o prop_name ); - String::from_utf8_lossy(binary_string.as_ref()) + String::from_utf8_lossy(binary_string) } }; instance.builder.set_name(value); @@ -406,7 +403,7 @@ This may cause unexpected or broken behavior in your final results if you rely o self.deserializer.database, binary_type, type_info.type_name, - prop_name.as_str().into(), + prop_name.into(), ) { property } else { @@ -420,8 +417,8 @@ This may cause unexpected or broken behavior in your final results if you rely o VariantType::String => { for referent in &type_info.referents { let instance = self.instances_by_ref.get_mut(referent).unwrap(); - let binary_string = chunk.read_binary_string()?; - let value = match std::str::from_utf8(&binary_string) { + let binary_string = read_binary_string_slice(&mut chunk)?; + let value = match std::str::from_utf8(binary_string) { Ok(value) => Cow::Borrowed(value), Err(_) => { log::warn!( @@ -431,7 +428,7 @@ This may cause unexpected or broken behavior in your final results if you rely o property.name ); - String::from_utf8_lossy(&binary_string) + String::from_utf8_lossy(binary_string) } }; @@ -455,16 +452,15 @@ This may cause unexpected or broken behavior in your final results if you rely o VariantType::Tags => { for referent in &type_info.referents { let instance = self.instances_by_ref.get_mut(referent).unwrap(); - let buffer = chunk.read_binary_string()?; + let buffer = read_binary_string_slice(&mut chunk)?; - let value = Tags::decode(buffer.as_ref()).map_err(|_| { - InnerError::InvalidPropData { + let value = + Tags::decode(buffer).map_err(|_| InnerError::InvalidPropData { type_name: type_info.type_name.to_string(), - prop_name: prop_name.clone(), + prop_name: prop_name.to_owned(), valid_value: "a list of valid null-delimited UTF-8 strings", actual_value: "invalid UTF-8".to_string(), - } - })?; + })?; add_property(instance, &property, value.into()); } @@ -472,9 +468,9 @@ This may cause unexpected or broken behavior in your final results if you rely o VariantType::Attributes => { for referent in &type_info.referents { let instance = self.instances_by_ref.get_mut(referent).unwrap(); - let buffer = chunk.read_binary_string()?; + let buffer = read_binary_string_slice(&mut chunk)?; - match Attributes::from_reader(buffer.as_slice()) { + match Attributes::from_reader(buffer) { Ok(value) => { add_property(instance, &property, value.into()); } @@ -499,8 +495,8 @@ rbx-dom may require changes to fully support this property. Please open an issue VariantType::MaterialColors => { for referent in &type_info.referents { let instance = self.instances_by_ref.get_mut(referent).unwrap(); - let buffer = chunk.read_binary_string()?; - match MaterialColors::decode(&buffer) { + let buffer = read_binary_string_slice(&mut chunk)?; + match MaterialColors::decode(buffer) { Ok(value) => add_property(instance, &property, value.into()), Err(err) => { log::warn!( @@ -523,7 +519,7 @@ rbx-dom may require changes to fully support this property. Please open an issue invalid_type => { return Err(InnerError::PropTypeMismatch { type_name: type_info.type_name.to_string(), - prop_name, + prop_name: prop_name.to_owned(), valid_type_names: "String, ContentId, Content, Tags, Attributes, or BinaryString", actual_type_name: format!("{:?}", invalid_type), @@ -541,7 +537,7 @@ rbx-dom may require changes to fully support this property. Please open an issue invalid_type => { return Err(InnerError::PropTypeMismatch { type_name: type_info.type_name.to_string(), - prop_name, + prop_name: prop_name.to_owned(), valid_type_names: "Bool", actual_type_name: format!("{:?}", invalid_type), }); @@ -573,7 +569,7 @@ rbx-dom may require changes to fully support this property. Please open an issue invalid_type => { return Err(InnerError::PropTypeMismatch { type_name: type_info.type_name.to_string(), - prop_name, + prop_name: prop_name.to_owned(), valid_type_names: "Int32", actual_type_name: format!("{:?}", invalid_type), }); @@ -592,7 +588,7 @@ rbx-dom may require changes to fully support this property. Please open an issue invalid_type => { return Err(InnerError::PropTypeMismatch { type_name: type_info.type_name.to_string(), - prop_name, + prop_name: prop_name.to_owned(), valid_type_names: "Float32", actual_type_name: format!("{:?}", invalid_type), }); @@ -622,7 +618,7 @@ rbx-dom may require changes to fully support this property. Please open an issue invalid_type => { return Err(InnerError::PropTypeMismatch { type_name: type_info.type_name.to_string(), - prop_name, + prop_name: prop_name.to_owned(), valid_type_names: "Float64", actual_type_name: format!("{:?}", invalid_type), }); @@ -649,7 +645,7 @@ rbx-dom may require changes to fully support this property. Please open an issue invalid_type => { return Err(InnerError::PropTypeMismatch { type_name: type_info.type_name.to_string(), - prop_name, + prop_name: prop_name.to_owned(), valid_type_names: "UDim", actual_type_name: format!("{:?}", invalid_type), }); @@ -688,7 +684,7 @@ rbx-dom may require changes to fully support this property. Please open an issue invalid_type => { return Err(InnerError::PropTypeMismatch { type_name: type_info.type_name.to_string(), - prop_name, + prop_name: prop_name.to_owned(), valid_type_names: "UDim2", actual_type_name: format!("{:?}", invalid_type), }); @@ -720,7 +716,7 @@ rbx-dom may require changes to fully support this property. Please open an issue invalid_type => { return Err(InnerError::PropTypeMismatch { type_name: type_info.type_name.to_string(), - prop_name, + prop_name: prop_name.to_owned(), valid_type_names: "Ray", actual_type_name: format!("{:?}", invalid_type), }); @@ -734,7 +730,7 @@ rbx-dom may require changes to fully support this property. Please open an issue let faces = Faces::from_bits(value).ok_or_else(|| InnerError::InvalidPropData { type_name: type_info.type_name.to_string(), - prop_name: prop_name.clone(), + prop_name: prop_name.to_owned(), valid_value: "less than 63", actual_value: value.to_string(), })?; @@ -745,7 +741,7 @@ rbx-dom may require changes to fully support this property. Please open an issue invalid_type => { return Err(InnerError::PropTypeMismatch { type_name: type_info.type_name.to_string(), - prop_name, + prop_name: prop_name.to_owned(), valid_type_names: "Faces", actual_type_name: format!("{:?}", invalid_type), }); @@ -760,7 +756,7 @@ rbx-dom may require changes to fully support this property. Please open an issue let axes = Axes::from_bits(value).ok_or_else(|| InnerError::InvalidPropData { type_name: type_info.type_name.to_string(), - prop_name: prop_name.clone(), + prop_name: prop_name.to_owned(), valid_value: "less than 7", actual_value: value.to_string(), })?; @@ -771,7 +767,7 @@ rbx-dom may require changes to fully support this property. Please open an issue invalid_type => { return Err(InnerError::PropTypeMismatch { type_name: type_info.type_name.to_string(), - prop_name, + prop_name: prop_name.to_owned(), valid_type_names: "Axes", actual_type_name: format!("{:?}", invalid_type), }); @@ -790,7 +786,7 @@ rbx-dom may require changes to fully support this property. Please open an issue .and_then(BrickColor::from_number) .ok_or_else(|| InnerError::InvalidPropData { type_name: type_info.type_name.to_string(), - prop_name: prop_name.clone(), + prop_name: prop_name.to_owned(), valid_value: "a valid BrickColor", actual_value: value.to_string(), })?; @@ -801,7 +797,7 @@ rbx-dom may require changes to fully support this property. Please open an issue invalid_type => { return Err(InnerError::PropTypeMismatch { type_name: type_info.type_name.to_string(), - prop_name, + prop_name: prop_name.to_owned(), valid_type_names: "BrickColor", actual_type_name: format!("{:?}", invalid_type), }); @@ -831,7 +827,7 @@ rbx-dom may require changes to fully support this property. Please open an issue invalid_type => { return Err(InnerError::PropTypeMismatch { type_name: type_info.type_name.to_string(), - prop_name, + prop_name: prop_name.to_owned(), valid_type_names: "Color3", actual_type_name: format!("{:?}", invalid_type), }); @@ -855,7 +851,7 @@ rbx-dom may require changes to fully support this property. Please open an issue invalid_type => { return Err(InnerError::PropTypeMismatch { type_name: type_info.type_name.to_string(), - prop_name, + prop_name: prop_name.to_owned(), valid_type_names: "Vector2", actual_type_name: format!("{:?}", invalid_type), }); @@ -885,7 +881,7 @@ rbx-dom may require changes to fully support this property. Please open an issue invalid_type => { return Err(InnerError::PropTypeMismatch { type_name: type_info.type_name.to_string(), - prop_name, + prop_name: prop_name.to_owned(), valid_type_names: "Vector3", actual_type_name: format!("{:?}", invalid_type), }); @@ -921,7 +917,7 @@ rbx-dom may require changes to fully support this property. Please open an issue } else { return Err(InnerError::BadRotationId { type_name: type_info.type_name.to_string(), - prop_name, + prop_name: prop_name.to_owned(), id, }); } @@ -951,7 +947,7 @@ rbx-dom may require changes to fully support this property. Please open an issue invalid_type => { return Err(InnerError::PropTypeMismatch { type_name: type_info.type_name.to_string(), - prop_name, + prop_name: prop_name.to_owned(), valid_type_names: "CFrame", actual_type_name: format!("{:?}", invalid_type), }); @@ -970,7 +966,7 @@ rbx-dom may require changes to fully support this property. Please open an issue invalid_type => { return Err(InnerError::PropTypeMismatch { type_name: type_info.type_name.to_string(), - prop_name, + prop_name: prop_name.to_owned(), valid_type_names: "Enum", actual_type_name: format!("{:?}", invalid_type), }); @@ -995,7 +991,7 @@ rbx-dom may require changes to fully support this property. Please open an issue invalid_type => { return Err(InnerError::PropTypeMismatch { type_name: type_info.type_name.to_string(), - prop_name, + prop_name: prop_name.to_owned(), valid_type_names: "Ref", actual_type_name: format!("{:?}", invalid_type), }); @@ -1020,7 +1016,7 @@ rbx-dom may require changes to fully support this property. Please open an issue invalid_type => { return Err(InnerError::PropTypeMismatch { type_name: type_info.type_name.to_string(), - prop_name, + prop_name: prop_name.to_owned(), valid_type_names: "Vector3int16", actual_type_name: format!("{:?}", invalid_type), }); @@ -1058,7 +1054,7 @@ rbx-dom may require changes to fully support this property. Please open an issue invalid_type => { return Err(InnerError::PropTypeMismatch { type_name: type_info.type_name.to_string(), - prop_name, + prop_name: prop_name.to_owned(), valid_type_names: "Font", actual_type_name: format!("{:?}", invalid_type), }); @@ -1085,7 +1081,7 @@ rbx-dom may require changes to fully support this property. Please open an issue invalid_type => { return Err(InnerError::PropTypeMismatch { type_name: type_info.type_name.to_string(), - prop_name, + prop_name: prop_name.to_owned(), valid_type_names: "NumberSequence", actual_type_name: format!("{:?}", invalid_type), }); @@ -1118,7 +1114,7 @@ rbx-dom may require changes to fully support this property. Please open an issue invalid_type => { return Err(InnerError::PropTypeMismatch { type_name: type_info.type_name.to_string(), - prop_name, + prop_name: prop_name.to_owned(), valid_type_names: "ColorSequence", actual_type_name: format!("{:?}", invalid_type), }); @@ -1138,7 +1134,7 @@ rbx-dom may require changes to fully support this property. Please open an issue invalid_type => { return Err(InnerError::PropTypeMismatch { type_name: type_info.type_name.to_string(), - prop_name, + prop_name: prop_name.to_owned(), valid_type_names: "NumberRange", actual_type_name: format!("{:?}", invalid_type), }); @@ -1171,7 +1167,7 @@ rbx-dom may require changes to fully support this property. Please open an issue invalid_type => { return Err(InnerError::PropTypeMismatch { type_name: type_info.type_name.to_string(), - prop_name, + prop_name: prop_name.to_owned(), valid_type_names: "Rect", actual_type_name: format!("{:?}", invalid_type), }); @@ -1201,7 +1197,7 @@ rbx-dom may require changes to fully support this property. Please open an issue invalid_type => { return Err(InnerError::PropTypeMismatch { type_name: type_info.type_name.to_string(), - prop_name, + prop_name: prop_name.to_owned(), valid_type_names: "PhysicalProperties", actual_type_name: format!("{:?}", invalid_type), }); @@ -1232,7 +1228,7 @@ rbx-dom may require changes to fully support this property. Please open an issue invalid_type => { return Err(InnerError::PropTypeMismatch { type_name: type_info.type_name.to_string(), - prop_name, + prop_name: prop_name.to_owned(), valid_type_names: "Color3", actual_type_name: format!("{:?}", invalid_type), }); @@ -1251,7 +1247,7 @@ rbx-dom may require changes to fully support this property. Please open an issue invalid_type => { return Err(InnerError::PropTypeMismatch { type_name: type_info.type_name.to_string(), - prop_name, + prop_name: prop_name.to_owned(), valid_type_names: "Int64", actual_type_name: format!("{:?}", invalid_type), }); @@ -1267,7 +1263,7 @@ rbx-dom may require changes to fully support this property. Please open an issue self.shared_strings.get(value as usize).ok_or_else(|| { InnerError::InvalidPropData { type_name: type_info.type_name.to_string(), - prop_name: prop_name.clone(), + prop_name: prop_name.to_owned(), valid_value: "a valid SharedString", actual_value: format!("{:?}", value), } @@ -1281,7 +1277,7 @@ rbx-dom may require changes to fully support this property. Please open an issue invalid_type => { return Err(InnerError::PropTypeMismatch { type_name: type_info.type_name.to_string(), - prop_name, + prop_name: prop_name.to_owned(), valid_type_names: "SharedString", actual_type_name: format!("{:?}", invalid_type), }) @@ -1329,7 +1325,7 @@ rbx-dom may require changes to fully support this property. Please open an issue } else { return Err(InnerError::BadRotationId { type_name: type_info.type_name.to_string(), - prop_name, + prop_name: prop_name.to_owned(), id, }); } @@ -1377,7 +1373,7 @@ rbx-dom may require changes to fully support this property. Please open an issue invalid_type => { return Err(InnerError::PropTypeMismatch { type_name: type_info.type_name.to_string(), - prop_name, + prop_name: prop_name.to_owned(), valid_type_names: "OptionalCFrame", actual_type_name: format!("{:?}", invalid_type), }); @@ -1407,7 +1403,7 @@ rbx-dom may require changes to fully support this property. Please open an issue invalid_type => { return Err(InnerError::PropTypeMismatch { type_name: type_info.type_name.to_string(), - prop_name, + prop_name: prop_name.to_owned(), valid_type_names: "UniqueId", actual_type_name: format!("{:?}", invalid_type), }); @@ -1432,7 +1428,7 @@ rbx-dom may require changes to fully support this property. Please open an issue invalid_type => { return Err(InnerError::PropTypeMismatch { type_name: type_info.type_name.to_string(), - prop_name, + prop_name: prop_name.to_owned(), valid_type_names: "SecurityCapabilities", actual_type_name: format!("{:?}", invalid_type), }); @@ -1483,7 +1479,7 @@ rbx-dom may require changes to fully support this property. Please open an issue invalid_type => { return Err(InnerError::PropTypeMismatch { type_name: type_info.type_name.to_string(), - prop_name, + prop_name: prop_name.to_owned(), valid_type_names: "Content", actual_type_name: format!("{:?}", invalid_type), });