use std::{
collections::{HashMap, HashSet},
io::Read,
};
use log::trace;
use rbx_dom_weak::{
types::{Ref, SharedString, Variant, VariantType},
InstanceBuilder, WeakDom,
};
use rbx_reflection::{DataType, PropertyKind, PropertySerialization, ReflectionDatabase};
use crate::{
conversion::ConvertVariant,
core::find_canonical_property_descriptor,
error::{DecodeError, DecodeErrorKind},
types::read_value_xml,
};
use crate::deserializer_core::{XmlEventReader, XmlReadEvent};
pub fn decode_internal<R: Read>(source: R, options: DecodeOptions) -> Result<WeakDom, DecodeError> {
let mut tree = WeakDom::new(InstanceBuilder::new("DataModel"));
let root_id = tree.root_ref();
let mut iterator = XmlEventReader::from_source(source);
let mut state = ParseState::new(&mut tree, options);
deserialize_root(&mut iterator, &mut state, root_id)?;
apply_referent_rewrites(&mut state);
apply_shared_string_rewrites(&mut state);
Ok(tree)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum DecodePropertyBehavior {
IgnoreUnknown,
ReadUnknown,
ErrorOnUnknown,
NoReflection,
}
#[derive(Debug, Clone)]
pub struct DecodeOptions<'db> {
property_behavior: DecodePropertyBehavior,
database: &'db ReflectionDatabase<'db>,
}
impl<'db> DecodeOptions<'db> {
#[inline]
pub fn new() -> Self {
DecodeOptions {
property_behavior: DecodePropertyBehavior::IgnoreUnknown,
database: rbx_reflection_database::get(),
}
}
#[inline]
pub fn property_behavior(self, property_behavior: DecodePropertyBehavior) -> Self {
DecodeOptions {
property_behavior,
..self
}
}
#[inline]
pub fn reflection_database(self, database: &'db ReflectionDatabase<'db>) -> Self {
DecodeOptions { database, ..self }
}
pub(crate) fn use_reflection(&self) -> bool {
self.property_behavior != DecodePropertyBehavior::NoReflection
}
}
impl<'db> Default for DecodeOptions<'db> {
fn default() -> DecodeOptions<'db> {
DecodeOptions::new()
}
}
pub struct ParseState<'dom, 'db> {
tree: &'dom mut WeakDom,
options: DecodeOptions<'db>,
metadata: HashMap<String, String>,
referents_to_ids: HashMap<String, Ref>,
referent_rewrites: Vec<ReferentRewrite>,
known_shared_strings: HashMap<String, SharedString>,
shared_string_rewrites: Vec<SharedStringRewrite>,
unknown_type_names: HashSet<String>,
}
struct ReferentRewrite {
id: Ref,
property_name: String,
referent_value: String,
}
struct SharedStringRewrite {
id: Ref,
property_name: String,
shared_string_hash: String,
}
impl<'dom, 'db> ParseState<'dom, 'db> {
fn new(tree: &'dom mut WeakDom, options: DecodeOptions<'db>) -> ParseState<'dom, 'db> {
ParseState {
tree,
options,
metadata: HashMap::new(),
referents_to_ids: HashMap::new(),
referent_rewrites: Vec::new(),
known_shared_strings: HashMap::new(),
shared_string_rewrites: Vec::new(),
unknown_type_names: HashSet::new(),
}
}
pub fn unknown_type_visited(&mut self, id: Ref, property_name: &str, type_name: &str) {
if self.unknown_type_names.contains(type_name) {
return;
}
self.unknown_type_names.insert(type_name.to_owned());
let instance = self.tree.get_by_ref(id).unwrap();
log::warn!(
"Unknown value type name \"{name}\" in Roblox XML model file. \
Found in property {class}.{prop}.",
name = type_name,
class = instance.class,
prop = property_name,
);
}
pub fn add_referent_rewrite(&mut self, id: Ref, property_name: String, referent_value: String) {
self.referent_rewrites.push(ReferentRewrite {
id,
property_name,
referent_value,
});
}
pub fn add_shared_string_rewrite(
&mut self,
id: Ref,
property_name: String,
shared_string_hash: String,
) {
self.shared_string_rewrites.push(SharedStringRewrite {
id,
property_name,
shared_string_hash,
});
}
}
fn apply_referent_rewrites(state: &mut ParseState) {
for rewrite in &state.referent_rewrites {
let new_value = match state.referents_to_ids.get(&rewrite.referent_value) {
Some(id) => *id,
None => continue,
};
let instance = state
.tree
.get_by_ref_mut(rewrite.id)
.expect("rbx_xml bug: had ID in referent rewrite list that didn't end up in the tree");
instance
.properties
.insert(rewrite.property_name.clone(), Variant::Ref(new_value));
}
}
fn apply_shared_string_rewrites(state: &mut ParseState) {
for rewrite in &state.shared_string_rewrites {
let new_value = match state.known_shared_strings.get(&rewrite.shared_string_hash) {
Some(v) => v.clone(),
None => continue,
};
let instance = state.tree.get_by_ref_mut(rewrite.id).expect(
"rbx_xml bug: had ID in SharedString rewrite list that didn't end up in the tree",
);
instance.properties.insert(
rewrite.property_name.clone(),
Variant::SharedString(new_value),
);
}
}
fn deserialize_root<R: Read>(
reader: &mut XmlEventReader<R>,
state: &mut ParseState,
parent_id: Ref,
) -> Result<(), DecodeError> {
match reader.expect_next()? {
XmlReadEvent::StartDocument { .. } => {}
_ => unreachable!(),
}
let doc_attributes = reader.expect_start_with_name("roblox")?;
let mut doc_version = None;
for attribute in doc_attributes.into_iter() {
if attribute.name.local_name.as_str() == "version" {
doc_version = Some(attribute.value);
}
}
let doc_version =
doc_version.ok_or_else(|| reader.error(DecodeErrorKind::MissingAttribute("version")))?;
if doc_version != "4" {
return Err(reader.error(DecodeErrorKind::WrongDocVersion(doc_version)));
}
loop {
match reader.expect_peek()? {
XmlReadEvent::StartElement { name, .. } => {
match name.local_name.as_str() {
"Item" => {
deserialize_instance(reader, state, parent_id)?;
}
"External" => {
reader.eat_unknown_tag()?;
}
"Meta" => {
deserialize_metadata(reader, state)?;
}
"SharedStrings" => {
deserialize_shared_string_dict(reader, state)?;
}
_ => {
let event = reader.expect_next().unwrap();
return Err(reader.error(DecodeErrorKind::UnexpectedXmlEvent(event)));
}
}
}
XmlReadEvent::EndElement { name } => {
if name.local_name == "roblox" {
reader.expect_next().unwrap();
break;
} else {
let event = reader.expect_next().unwrap();
return Err(reader.error(DecodeErrorKind::UnexpectedXmlEvent(event)));
}
}
XmlReadEvent::EndDocument => break,
_ => {
let event = reader.expect_next().unwrap();
return Err(reader.error(DecodeErrorKind::UnexpectedXmlEvent(event)));
}
}
}
Ok(())
}
fn deserialize_metadata<R: Read>(
reader: &mut XmlEventReader<R>,
state: &mut ParseState,
) -> Result<(), DecodeError> {
let name = {
let attributes = reader.expect_start_with_name("Meta")?;
let mut name = None;
for attribute in attributes.into_iter() {
if attribute.name.local_name.as_str() == "name" {
name = Some(attribute.value);
}
}
name.ok_or_else(|| reader.error(DecodeErrorKind::MissingAttribute("name")))?
};
let value = reader.read_characters()?;
reader.expect_end_with_name("Meta")?;
state.metadata.insert(name, value);
Ok(())
}
fn deserialize_shared_string_dict<R: Read>(
reader: &mut XmlEventReader<R>,
state: &mut ParseState,
) -> Result<(), DecodeError> {
reader.expect_start_with_name("SharedStrings")?;
loop {
match reader.expect_peek()? {
XmlReadEvent::StartElement { name, .. } => {
if name.local_name == "SharedString" {
deserialize_shared_string(reader, state)?;
} else {
let event = reader.expect_next().unwrap();
return Err(reader.error(DecodeErrorKind::UnexpectedXmlEvent(event)));
}
}
XmlReadEvent::EndElement { name } => {
if name.local_name == "SharedStrings" {
break;
} else {
let event = reader.expect_next().unwrap();
return Err(reader.error(DecodeErrorKind::UnexpectedXmlEvent(event)));
}
}
_ => {
let event = reader.expect_next().unwrap();
return Err(reader.error(DecodeErrorKind::UnexpectedXmlEvent(event)));
}
}
}
reader.expect_end_with_name("SharedStrings")?;
Ok(())
}
fn deserialize_shared_string<R: Read>(
reader: &mut XmlEventReader<R>,
state: &mut ParseState,
) -> Result<(), DecodeError> {
let attributes = reader.expect_start_with_name("SharedString")?;
let mut md5_hash = None;
for attribute in attributes.into_iter() {
if attribute.name.local_name == "md5" {
md5_hash = Some(attribute.value);
break;
}
}
let md5_hash =
md5_hash.ok_or_else(|| reader.error(DecodeErrorKind::MissingAttribute("md5")))?;
let buffer = reader.read_base64_characters()?;
let value = SharedString::new(buffer);
state.known_shared_strings.insert(md5_hash, value);
reader.expect_end_with_name("SharedString")?;
Ok(())
}
fn deserialize_instance<R: Read>(
reader: &mut XmlEventReader<R>,
state: &mut ParseState,
parent_id: Ref,
) -> Result<(), DecodeError> {
let (class_name, referent) = {
let attributes = reader.expect_start_with_name("Item")?;
let mut class = None;
let mut referent = None;
for attribute in attributes.into_iter() {
match attribute.name.local_name.as_str() {
"class" => class = Some(attribute.value),
"referent" => referent = Some(attribute.value),
_ => {}
}
}
let class =
class.ok_or_else(|| reader.error(DecodeErrorKind::MissingAttribute("class")))?;
(class, referent)
};
trace!("Class {} with referent {:?}", class_name, referent);
let builder = InstanceBuilder::new(class_name);
let instance_id = state.tree.insert(parent_id, builder);
if let Some(referent) = referent {
state.referents_to_ids.insert(referent, instance_id);
}
let mut properties: HashMap<String, Variant> = HashMap::new();
loop {
match reader.expect_peek()? {
XmlReadEvent::StartElement { name, .. } => match name.local_name.as_str() {
"Properties" => {
deserialize_properties(reader, state, instance_id, &mut properties)?;
}
"Item" => {
deserialize_instance(reader, state, instance_id)?;
}
_ => {
let event = reader.expect_next().unwrap();
return Err(reader.error(DecodeErrorKind::UnexpectedXmlEvent(event)));
}
},
XmlReadEvent::EndElement { name } => {
if name.local_name != "Item" {
let event = reader.expect_next().unwrap();
return Err(reader.error(DecodeErrorKind::UnexpectedXmlEvent(event)));
}
reader.expect_next().unwrap();
break;
}
_ => {
let event = reader.expect_next().unwrap();
return Err(reader.error(DecodeErrorKind::UnexpectedXmlEvent(event)));
}
}
}
let instance = state.tree.get_by_ref_mut(instance_id).unwrap();
instance.name = match properties.remove("Name") {
Some(value) => match value {
Variant::String(value) => value,
_ => return Err(reader.error(DecodeErrorKind::NameMustBeString(value.ty()))),
},
None => instance.class.clone(),
};
instance.properties = properties;
Ok(())
}
fn deserialize_properties<R: Read>(
reader: &mut XmlEventReader<R>,
state: &mut ParseState,
instance_id: Ref,
props: &mut HashMap<String, Variant>,
) -> Result<(), DecodeError> {
reader.expect_start_with_name("Properties")?;
let class_name = state
.tree
.get_by_ref(instance_id)
.expect("Couldn't find instance to deserialize properties into")
.class
.clone();
log::trace!(
"Deserializing properties for instance {:?}, whose ClassName is {}",
instance_id,
class_name
);
loop {
let (xml_type_name, xml_property_name) = {
match reader.expect_peek()? {
XmlReadEvent::StartElement {
name, attributes, ..
} => {
let mut xml_property_name = None;
for attribute in attributes {
if attribute.name.local_name.as_str() == "name" {
xml_property_name = Some(attribute.value.to_owned());
break;
}
}
let xml_property_name = match xml_property_name {
Some(value) => value,
None => return Err(reader.error(DecodeErrorKind::MissingAttribute("name"))),
};
(name.local_name.to_owned(), xml_property_name)
}
XmlReadEvent::EndElement { name } => {
if name.local_name == "Properties" {
reader.expect_next()?;
return Ok(());
} else {
let err = DecodeErrorKind::UnexpectedXmlEvent(reader.expect_next()?);
return Err(reader.error(err));
}
}
_ => {
let err = DecodeErrorKind::UnexpectedXmlEvent(reader.expect_next()?);
return Err(reader.error(err));
}
}
};
log::trace!(
"Deserializing property {}.{}, of type {}",
class_name,
xml_property_name,
xml_type_name
);
let maybe_descriptor = if state.options.use_reflection() {
find_canonical_property_descriptor(
&class_name,
&xml_property_name,
state.options.database,
)
} else {
None
};
if let Some(descriptor) = maybe_descriptor {
let value =
match read_value_xml(reader, state, &xml_type_name, instance_id, &descriptor.name)?
{
Some(value) => value,
None => continue,
};
let xml_ty = value.ty();
let expected_type = match &descriptor.data_type {
DataType::Value(data_type) => *data_type,
DataType::Enum(_enum_name) => VariantType::Enum,
_ => unimplemented!(),
};
log::trace!("property's read type: {xml_ty:?}, canonical type: {expected_type:?}");
let value = match value.try_convert(expected_type) {
Ok(value) => value,
Err(message) => {
return Err(
reader.error(DecodeErrorKind::UnsupportedPropertyConversion {
class_name: class_name.clone(),
property_name: descriptor.name.to_string(),
expected_type,
actual_type: xml_ty,
message,
}),
);
}
};
match &descriptor.kind {
PropertyKind::Canonical {
serialization: PropertySerialization::Migrate(migration),
} => {
let new_property_name = &migration.new_property_name;
let old_property_name = &descriptor.name;
if !props.contains_key(new_property_name) {
log::trace!(
"Attempting to migrate property {old_property_name} to {new_property_name}"
);
match migration.perform(&value) {
Ok(migrated_value) => {
props.insert(new_property_name.to_string(), migrated_value);
log::trace!(
"Successfully migrated property {old_property_name} to {new_property_name}"
);
}
Err(error) => {
return Err(reader.error(DecodeErrorKind::MigrationError(error)));
}
}
}
}
_ => {
props.insert(descriptor.name.to_string(), value);
}
};
} else {
match state.options.property_behavior {
DecodePropertyBehavior::IgnoreUnknown => {
read_value_xml(
reader,
state,
&xml_type_name,
instance_id,
&xml_property_name,
)?;
}
DecodePropertyBehavior::ReadUnknown | DecodePropertyBehavior::NoReflection => {
let value = match read_value_xml(
reader,
state,
&xml_type_name,
instance_id,
&xml_property_name,
)? {
Some(value) => value,
None => continue,
};
props.insert(xml_property_name, value);
}
DecodePropertyBehavior::ErrorOnUnknown => {
return Err(reader.error(DecodeErrorKind::UnknownProperty {
class_name,
property_name: xml_property_name,
}));
}
}
}
}
}