diff --git a/docs/attributes.md b/docs/attributes.md index fda407fca..ff5132762 100644 --- a/docs/attributes.md +++ b/docs/attributes.md @@ -18,7 +18,7 @@ This document describes the Attribute binary format. In this format there is no - [Vector2](#vector2) - [Vector3](#vector3) - [CFrame](#cframe) - - [EnumItem](#EnumItem) + - [EnumItem](#enumitem) - [NumberSequence](#numbersequence) - [ColorSequence](#colorsequence) - [NumberRange](#numberrange) @@ -27,7 +27,7 @@ This document describes the Attribute binary format. In this format there is no ## Document Conventions -This document assumes a basic understanding of Rust's convention for numeric types. For example: +This document assumes a basic understanding of Rust's convention for numeric types. For example: - `u32` is an unsigned 32-bit integer - `f32` is a 32-bit floating point @@ -189,7 +189,7 @@ Demonstrating the axis-aligned rotation matrix case, a `CFrame` with the value ` ### EnumItem **Type ID `0x15`** -The `EnumItem` type is composed of two parts: +The `EnumItem` type is composed of two parts: | Field Name | Format | Value | |:-----------|:--------------------|:-------------------------------------------------------| diff --git a/rbx_binary/src/serializer/state.rs b/rbx_binary/src/serializer/state.rs index 168ee818a..e83e11db3 100644 --- a/rbx_binary/src/serializer/state.rs +++ b/rbx_binary/src/serializer/state.rs @@ -9,8 +9,8 @@ use ahash::{HashMap, HashMapExt, HashSetExt}; use rbx_dom_weak::{ types::{ Attributes, Axes, BinaryString, BrickColor, CFrame, Color3, Color3uint8, ColorSequence, - ColorSequenceKeypoint, Content, Enum, Faces, Font, MaterialColors, Matrix3, NumberRange, - NumberSequence, NumberSequenceKeypoint, PhysicalProperties, Ray, Rect, Ref, + ColorSequenceKeypoint, Content, Enum, EnumItem, Faces, Font, MaterialColors, Matrix3, + NumberRange, NumberSequence, NumberSequenceKeypoint, PhysicalProperties, Ray, Rect, Ref, SecurityCapabilities, SharedString, Tags, UDim, UDim2, UniqueId, Variant, VariantType, Vector2, Vector3, Vector3int16, }, @@ -970,10 +970,10 @@ impl<'dom, 'db, W: Write> SerializerState<'dom, 'db, W> { let mut buf = Vec::with_capacity(values.len()); for (i, rbx_value) in values { - if let Variant::Enum(value) = rbx_value.as_ref() { - buf.push(value.to_u32()); - } else { - return type_mismatch(i, &rbx_value, "Enum"); + match rbx_value.as_ref() { + Variant::Enum(value) => buf.push(value.to_u32()), + Variant::EnumItem(EnumItem { value, .. }) => buf.push(*value), + _ => return type_mismatch(i, &rbx_value, "Enum or EnumItem"), } } diff --git a/rbx_binary/src/tests/models.rs b/rbx_binary/src/tests/models.rs index 5105d6bcb..d47df8493 100644 --- a/rbx_binary/src/tests/models.rs +++ b/rbx_binary/src/tests/models.rs @@ -66,4 +66,5 @@ binary_tests! { folder_with_font_attribute, number_values_with_security_capabilities, lighting_with_int32_attribute, + folder_with_enum_attribute, } diff --git a/rbx_binary/src/tests/snapshots/rbx_binary__tests__util__folder-with-enum-attribute__decoded.snap b/rbx_binary/src/tests/snapshots/rbx_binary__tests__util__folder-with-enum-attribute__decoded.snap new file mode 100644 index 000000000..a6ce75538 --- /dev/null +++ b/rbx_binary/src/tests/snapshots/rbx_binary__tests__util__folder-with-enum-attribute__decoded.snap @@ -0,0 +1,23 @@ +--- +source: rbx_binary/src/tests/util.rs +expression: decoded_viewed +--- +- referent: referent-0 + name: Folder + class: Folder + properties: + Attributes: + Attributes: + AnEnumValue: + EnumItem: + type: Material + value: 512 + Capabilities: + SecurityCapabilities: 0 + Sandboxed: + Bool: false + SourceAssetId: + Int64: -1 + Tags: + Tags: [] + children: [] diff --git a/rbx_binary/src/tests/snapshots/rbx_binary__tests__util__folder-with-enum-attribute__encoded.snap b/rbx_binary/src/tests/snapshots/rbx_binary__tests__util__folder-with-enum-attribute__encoded.snap new file mode 100644 index 000000000..51d6284ba --- /dev/null +++ b/rbx_binary/src/tests/snapshots/rbx_binary__tests__util__folder-with-enum-attribute__encoded.snap @@ -0,0 +1,55 @@ +--- +source: rbx_binary/src/tests/util.rs +expression: text_roundtrip +--- +num_types: 1 +num_instances: 1 +chunks: + - Inst: + type_id: 0 + type_name: Folder + object_format: 0 + referents: + - 0 + - Prop: + type_id: 0 + prop_name: AttributesSerialize + prop_type: String + values: + - "\u0001\u0000\u0000\u0000\u000b\u0000\u0000\u0000AnEnumValue\u0015\b\u0000\u0000\u0000Material\u0000\u0002\u0000\u0000" + - Prop: + type_id: 0 + prop_name: Capabilities + prop_type: SecurityCapabilities + values: + - 0 + - Prop: + type_id: 0 + prop_name: Name + prop_type: String + values: + - Folder + - Prop: + type_id: 0 + prop_name: DefinesCapabilities + prop_type: Bool + values: + - false + - Prop: + type_id: 0 + prop_name: SourceAssetId + prop_type: Int64 + values: + - -1 + - Prop: + type_id: 0 + prop_name: Tags + prop_type: String + values: + - "" + - Prnt: + version: 0 + links: + - - 0 + - -1 + - End diff --git a/rbx_binary/src/tests/snapshots/rbx_binary__tests__util__folder-with-enum-attribute__input.snap b/rbx_binary/src/tests/snapshots/rbx_binary__tests__util__folder-with-enum-attribute__input.snap new file mode 100644 index 000000000..815d4131f --- /dev/null +++ b/rbx_binary/src/tests/snapshots/rbx_binary__tests__util__folder-with-enum-attribute__input.snap @@ -0,0 +1,59 @@ +--- +source: rbx_binary/src/tests/util.rs +expression: text_decoded +--- +num_types: 1 +num_instances: 1 +chunks: + - Meta: + entries: + - - ExplicitAutoJoints + - "true" + - Inst: + type_id: 0 + type_name: Folder + object_format: 0 + referents: + - 0 + - Prop: + type_id: 0 + prop_name: AttributesSerialize + prop_type: String + values: + - "\u0001\u0000\u0000\u0000\u000b\u0000\u0000\u0000AnEnumValue\u0015\b\u0000\u0000\u0000Material\u0000\u0002\u0000\u0000" + - Prop: + type_id: 0 + prop_name: Capabilities + prop_type: SecurityCapabilities + values: + - 0 + - Prop: + type_id: 0 + prop_name: DefinesCapabilities + prop_type: Bool + values: + - false + - Prop: + type_id: 0 + prop_name: Name + prop_type: String + values: + - Folder + - Prop: + type_id: 0 + prop_name: SourceAssetId + prop_type: Int64 + values: + - -1 + - Prop: + type_id: 0 + prop_name: Tags + prop_type: String + values: + - "" + - Prnt: + version: 0 + links: + - - 0 + - -1 + - End diff --git a/rbx_dom_lua/src/EncodedValue.lua b/rbx_dom_lua/src/EncodedValue.lua index f2e9e3434..67d78e45c 100644 --- a/rbx_dom_lua/src/EncodedValue.lua +++ b/rbx_dom_lua/src/EncodedValue.lua @@ -205,6 +205,19 @@ types = { end, }, + EnumItem = { + fromPod = function(pod) + return Enum[pod.type]:FromValue(pod.value) + end, + + toPod = function(roblox) + return { + type = tostring(roblox.EnumType), + value = roblox.Value, + } + end, + }, + Faces = { fromPod = function(pod) local faces = {} diff --git a/rbx_dom_lua/src/allValues.json b/rbx_dom_lua/src/allValues.json index 9b07d7bf0..59e531fb9 100644 --- a/rbx_dom_lua/src/allValues.json +++ b/rbx_dom_lua/src/allValues.json @@ -15,6 +15,12 @@ 0.0 ] }, + "TestEnumItem": { + "EnumItem": { + "type": "Material", + "value": 256 + } + }, "TestNumber": { "Float64": 1337.0 }, @@ -182,6 +188,15 @@ }, "ty": "Enum" }, + "EnumItem": { + "value": { + "EnumItem": { + "type": "Material", + "value": 256 + } + }, + "ty": "EnumItem" + }, "Faces": { "value": { "Faces": [ diff --git a/rbx_dom_lua/test b/rbx_dom_lua/test old mode 100644 new mode 100755 index 9ac1092f5..09577d651 --- a/rbx_dom_lua/test +++ b/rbx_dom_lua/test @@ -1,4 +1,5 @@ #!/bin/sh +cargo run --bin rbx_reflector values ./src/allValues.json rojo build test-place.project.json -o TestPlace.rbxlx -run-in-roblox --script run-tests.lua --place TestPlace.rbxlx \ No newline at end of file +run-in-roblox --script run-tests.lua --place TestPlace.rbxlx diff --git a/rbx_reflector/src/cli/values.rs b/rbx_reflector/src/cli/values.rs index 387cfbc7f..3cccb1391 100644 --- a/rbx_reflector/src/cli/values.rs +++ b/rbx_reflector/src/cli/values.rs @@ -4,10 +4,10 @@ use anyhow::bail; use clap::Parser; use rbx_types::{ Attributes, Axes, BinaryString, BrickColor, CFrame, Color3, Color3uint8, ColorSequence, - ColorSequenceKeypoint, Content, CustomPhysicalProperties, Enum, Faces, Font, MaterialColors, - Matrix3, NumberRange, NumberSequence, NumberSequenceKeypoint, PhysicalProperties, Ray, Rect, - Region3int16, Tags, TerrainMaterials, UDim, UDim2, Variant, VariantType, Vector2, Vector2int16, - Vector3, Vector3int16, + ColorSequenceKeypoint, Content, CustomPhysicalProperties, Enum, EnumItem, Faces, Font, + MaterialColors, Matrix3, NumberRange, NumberSequence, NumberSequenceKeypoint, + PhysicalProperties, Ray, Rect, Region3int16, Tags, TerrainMaterials, UDim, UDim2, Variant, + VariantType, Vector2, Vector2int16, Vector3, Vector3int16, }; use serde::Serialize; @@ -45,6 +45,13 @@ impl ValuesSubcommand { "TestUDim2", UDim2::new(UDim::new(1.0, 2), UDim::new(3.0, 4)), ) + .with( + "TestEnumItem", + EnumItem { + ty: "Material".into(), + value: 256, + }, + ) .into(), ); values.insert("Axes", Axes::all().into()); @@ -80,6 +87,14 @@ impl ValuesSubcommand { ); values.insert("Content", Content::from("rbxassetid://12345").into()); values.insert("Enum", Enum::from_u32(1234).into()); + values.insert( + "EnumItem", + EnumItem { + ty: "Material".into(), + value: 256, + } + .into(), + ); values.insert("Faces", Faces::all().into()); values.insert("Float32", 15.0f32.into()); values.insert("Float64", 15123.0f64.into()); diff --git a/rbx_types/CHANGELOG.md b/rbx_types/CHANGELOG.md index c4c4ae351..79a578c54 100644 --- a/rbx_types/CHANGELOG.md +++ b/rbx_types/CHANGELOG.md @@ -5,7 +5,9 @@ # 1.10.0 (2024-08-22) * Add support for `Int32` values within `Attributes` ([#439]) * Add `len` and `is_empty` to `Tags` ([#438]) +* Added support for `EnumItem` attributes. [#470] +[#470]: https://github.com/rojo-rbx/rbx-dom/pull/470 [#438]: https://github.com/rojo-rbx/rbx-dom/pull/438 [#439]: https://github.com/rojo-rbx/rbx-dom/pull/439 diff --git a/rbx_types/src/attributes/error.rs b/rbx_types/src/attributes/error.rs index 06386239e..aba632449 100644 --- a/rbx_types/src/attributes/error.rs +++ b/rbx_types/src/attributes/error.rs @@ -30,6 +30,9 @@ pub(crate) enum AttributeError { #[error(transparent)] Io(#[from] std::io::Error), + #[error(transparent)] + Utf8(#[from] std::string::FromUtf8Error), + #[error(transparent)] BadAttributeValue(#[from] crate::Error), diff --git a/rbx_types/src/attributes/reader.rs b/rbx_types/src/attributes/reader.rs index 74c17ad50..a1b4d6780 100644 --- a/rbx_types/src/attributes/reader.rs +++ b/rbx_types/src/attributes/reader.rs @@ -4,7 +4,7 @@ use std::{ }; use crate::{ - BinaryString, BrickColor, CFrame, Color3, ColorSequence, ColorSequenceKeypoint, Font, + BinaryString, BrickColor, CFrame, Color3, ColorSequence, ColorSequenceKeypoint, EnumItem, Font, FontStyle, FontWeight, Matrix3, NumberRange, NumberSequence, NumberSequenceKeypoint, Rect, UDim, UDim2, Variant, VariantType, Vector2, Vector3, }; @@ -202,6 +202,17 @@ pub(crate) fn read_attributes( } .into(), + VariantType::EnumItem => { + let enum_type = read_string(&mut value)?; + let value = read_u32(&mut value)?; + + EnumItem { + ty: String::from_utf8(enum_type)?, + value, + } + } + .into(), + other => return Err(AttributeError::UnsupportedVariantType(other)), }; diff --git a/rbx_types/src/attributes/type_id.rs b/rbx_types/src/attributes/type_id.rs index 2af23ec7b..af5957279 100644 --- a/rbx_types/src/attributes/type_id.rs +++ b/rbx_types/src/attributes/type_id.rs @@ -41,7 +41,7 @@ type_ids! { // ??? => 0x12, // ??? => 0x13, CFrame => 0x14, - // ??? => 0x15, + EnumItem => 0x15, // ??? => 0x16, NumberSequence => 0x17, // ??? => 0x18, diff --git a/rbx_types/src/attributes/writer.rs b/rbx_types/src/attributes/writer.rs index bd32da72f..2684cadfa 100644 --- a/rbx_types/src/attributes/writer.rs +++ b/rbx_types/src/attributes/writer.rs @@ -99,6 +99,10 @@ pub(crate) fn write_attributes( font.cached_face_id.as_deref().unwrap_or_default(), )?; } + Variant::EnumItem(enum_item) => { + write_string(&mut writer, &enum_item.ty)?; + write_u32(&mut writer, enum_item.value)?; + } other_variant => unreachable!("variant {:?} was not implemented", other_variant), } diff --git a/rbx_types/src/basic_types.rs b/rbx_types/src/basic_types.rs index 4d6013119..174a431eb 100644 --- a/rbx_types/src/basic_types.rs +++ b/rbx_types/src/basic_types.rs @@ -2,13 +2,13 @@ use thiserror::Error; use crate::Error; -/// Represents any Roblox enum value. +/// Represents any Roblox EnumItem. /// /// Roblox enums are not strongly typed, so the meaning of a value depends on /// where they're assigned. /// /// A list of all enums and their values are available [on the Roblox Developer -/// Hub](https://developer.roblox.com/en-us/api-reference/enum). +/// Hub](https://create.roblox.com/docs/reference/engine/enums). #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr( feature = "serde", @@ -29,6 +29,26 @@ impl Enum { } } +/// Represents a specific Roblox EnumItem. +/// +/// A list of all enums and their values are available [on the Roblox Developer +/// Hub](https://create.roblox.com/docs/reference/engine/enums). +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize,))] +pub struct EnumItem { + #[serde(rename = "type")] + pub ty: String, + pub value: u32, +} + +impl From for Enum { + fn from(enum_item: EnumItem) -> Self { + Self { + value: enum_item.value, + } + } +} + /// The standard 2D vector type used in Roblox. /// /// ## See Also @@ -718,4 +738,15 @@ mod serde_test { "[[1.0,2.0,3.0],[4.0,5.0,6.0],[7.0,8.0,9.0]]", ); } + + #[test] + fn tagged_enum_json() { + test_ser( + EnumItem { + ty: "PlayTag".to_string(), + value: 3, + }, + r#"{"type":"PlayTag","value":3}"#, + ); + } } diff --git a/rbx_types/src/variant.rs b/rbx_types/src/variant.rs index 597c8747c..801a41b5a 100644 --- a/rbx_types/src/variant.rs +++ b/rbx_types/src/variant.rs @@ -1,8 +1,8 @@ use crate::{ Attributes, Axes, BinaryString, BrickColor, CFrame, Color3, Color3uint8, ColorSequence, - Content, Enum, Faces, Font, MaterialColors, NumberRange, NumberSequence, PhysicalProperties, - Ray, Rect, Ref, Region3, Region3int16, SecurityCapabilities, SharedString, Tags, UDim, UDim2, - UniqueId, Vector2, Vector2int16, Vector3, Vector3int16, + Content, Enum, EnumItem, Faces, Font, MaterialColors, NumberRange, NumberSequence, + PhysicalProperties, Ray, Rect, Ref, Region3, Region3int16, SecurityCapabilities, SharedString, + Tags, UDim, UDim2, UniqueId, Vector2, Vector2int16, Vector3, Vector3int16, }; /// Reduces boilerplate from listing different values of Variant by wrapping @@ -129,6 +129,7 @@ make_variant! { UniqueId(UniqueId), MaterialColors(MaterialColors), SecurityCapabilities(SecurityCapabilities), + EnumItem(EnumItem), } impl From<&'_ str> for Variant { diff --git a/rbx_xml/src/conversion.rs b/rbx_xml/src/conversion.rs index d84fc049a..0e9927685 100644 --- a/rbx_xml/src/conversion.rs +++ b/rbx_xml/src/conversion.rs @@ -4,6 +4,7 @@ use std::borrow::{Borrow, Cow}; use std::convert::TryInto; +use rbx_dom_weak::types::Enum; use rbx_dom_weak::{ types::{Attributes, BrickColor, Color3uint8, MaterialColors, Tags, Variant, VariantType}, Ustr, @@ -96,6 +97,9 @@ rbx-dom may require changes to fully support this property. Please open an issue } } } + (Variant::EnumItem(enum_item), VariantType::Enum) => { + Ok(Cow::Owned(Enum::from_u32(enum_item.value).into())) + } (_, _) => Ok(value), } } diff --git a/rbx_xml/src/tests/basic.rs b/rbx_xml/src/tests/basic.rs index e4540eb38..7169844f4 100644 --- a/rbx_xml/src/tests/basic.rs +++ b/rbx_xml/src/tests/basic.rs @@ -2,11 +2,11 @@ use rbx_dom_weak::types::{ Attributes, BinaryString, BrickColor, Color3, Color3uint8, ColorSequence, - ColorSequenceKeypoint, Enum, Font, MaterialColors, NumberRange, NumberSequence, - NumberSequenceKeypoint, Rect, Tags, TerrainMaterials, UDim, UDim2, UniqueId, Variant, Vector2, - Vector3, + ColorSequenceKeypoint, Enum, EnumItem, Font, MaterialColors, NumberRange, NumberSequence, + NumberSequenceKeypoint, Rect, Tags, TerrainMaterials, UDim, UDim2, UniqueId, Variant, + VariantType, Vector2, Vector3, }; -use rbx_dom_weak::{InstanceBuilder, WeakDom}; +use rbx_dom_weak::{ustr, InstanceBuilder, WeakDom}; #[test] fn with_bool() { @@ -352,3 +352,28 @@ fn bad_migrated_property() { crate::to_writer_default(&mut encoded, &tree, &[tree.root_ref()]).unwrap(); insta::assert_snapshot!(std::str::from_utf8(&encoded).unwrap()); } + +#[test] +fn enum_item_to_enum() { + let tree = WeakDom::new(InstanceBuilder::new("Part").with_property( + "Material", + EnumItem { + ty: "Material".into(), + value: 256, + }, + )); + + let mut encoded = Vec::new(); + crate::to_writer_default(&mut encoded, &tree, &[tree.root_ref()]).unwrap(); + + let decoded = crate::from_reader_default(encoded.as_slice()).unwrap(); + let prop_type = decoded + .get_by_ref(*decoded.root().children().first().unwrap()) + .unwrap() + .properties + .get(&ustr("Material")) + .unwrap() + .ty(); + + assert_eq!(prop_type, VariantType::Enum); +} diff --git a/rbx_xml/src/tests/models.rs b/rbx_xml/src/tests/models.rs index eaffd828a..b6c58abb3 100644 --- a/rbx_xml/src/tests/models.rs +++ b/rbx_xml/src/tests/models.rs @@ -67,4 +67,5 @@ model_tests! { folder_with_font_attribute, number_values_with_security_capabilities, lighting_with_int32_attribute, + folder_with_enum_attribute, } diff --git a/rbx_xml/src/tests/snapshots/rbx_xml__tests__folder-with-enum-attribute__decoded.snap b/rbx_xml/src/tests/snapshots/rbx_xml__tests__folder-with-enum-attribute__decoded.snap new file mode 100644 index 000000000..27baa1dff --- /dev/null +++ b/rbx_xml/src/tests/snapshots/rbx_xml__tests__folder-with-enum-attribute__decoded.snap @@ -0,0 +1,23 @@ +--- +source: rbx_xml/src/tests/mod.rs +expression: "DomViewer::new().view_children(&decoded)" +--- +- referent: referent-0 + name: Folder + class: Folder + properties: + Attributes: + Attributes: + AnEnumValue: + EnumItem: + type: Material + value: 512 + Capabilities: + SecurityCapabilities: 0 + Sandboxed: + Bool: false + SourceAssetId: + Int64: -1 + Tags: + Tags: [] + children: [] diff --git a/rbx_xml/src/tests/snapshots/rbx_xml__tests__folder-with-enum-attribute__roundtrip.snap b/rbx_xml/src/tests/snapshots/rbx_xml__tests__folder-with-enum-attribute__roundtrip.snap new file mode 100644 index 000000000..7f3f27041 --- /dev/null +++ b/rbx_xml/src/tests/snapshots/rbx_xml__tests__folder-with-enum-attribute__roundtrip.snap @@ -0,0 +1,23 @@ +--- +source: rbx_xml/src/tests/mod.rs +expression: "DomViewer::new().view_children(&roundtrip)" +--- +- referent: referent-0 + name: Folder + class: Folder + properties: + Attributes: + Attributes: + AnEnumValue: + EnumItem: + type: Material + value: 512 + Capabilities: + SecurityCapabilities: 0 + Sandboxed: + Bool: false + SourceAssetId: + Int64: -1 + Tags: + Tags: [] + children: []