From 3f689f5381eaee1fff0555924047acd9c4a5e7ba Mon Sep 17 00:00:00 2001 From: Micah Date: Fri, 19 Jul 2024 15:48:05 -0700 Subject: [PATCH] Add command for stripping a specific property from Instances to rbx-util (#426) When debugging an issue that cropped up on the Roblox devforums yesterday, I wanted an easy way to remove every instance of property from a file (specifically, `SurfaceAppearance.Color`). Given that there's no easy way to do this without converting a file to XML, removing the property, then converting it back, I think it'd be helpful to include in rbx-util in the future. --- rbx_util/CHANGELOG.md | 8 +++- rbx_util/Cargo.toml | 2 +- rbx_util/README.md | 7 +++- rbx_util/src/main.rs | 9 ++++- rbx_util/src/remove_prop.rs | 80 +++++++++++++++++++++++++++++++++++++ 5 files changed, 100 insertions(+), 6 deletions(-) create mode 100644 rbx_util/src/remove_prop.rs diff --git a/rbx_util/CHANGELOG.md b/rbx_util/CHANGELOG.md index 9bcc59b68..c62f29076 100644 --- a/rbx_util/CHANGELOG.md +++ b/rbx_util/CHANGELOG.md @@ -1,10 +1,14 @@ # Changelog +## Version 0.2.1 + +- Added `remove-prop` command to strip a property from a file + ## Version 0.2.0 - Refactor commands into seperate files - Add `verbosity` and `color` global flags to control logging and terminal color -# Version 0.1.0 +## Version 0.1.0 -- Initial implementation \ No newline at end of file +- Initial implementation diff --git a/rbx_util/Cargo.toml b/rbx_util/Cargo.toml index ad96e8cc0..c32cff4e0 100644 --- a/rbx_util/Cargo.toml +++ b/rbx_util/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rbx_util" -version = "0.2.0" +version = "0.2.1" description = "Utilities for working with Roblox model and place files" license = "MIT" documentation = "https://docs.rs/rbx_util" diff --git a/rbx_util/README.md b/rbx_util/README.md index a72c2a02b..fd91b05a8 100644 --- a/rbx_util/README.md +++ b/rbx_util/README.md @@ -1,4 +1,5 @@ # rbx_util + Command line tool to convert and inspect Roblox model and place files using the rbx-dom family of libraries. Usage: @@ -9,4 +10,8 @@ rbx-util convert input.rbxmx output.rbxm # Debug the contents of a binary model rbx-util view-binary output.rbxm -``` \ No newline at end of file + +# Strip the specified PropertyName from all Instances of ClassName in the provided input. +# Then, write the resulting file the provided output. +rbx-util remove-prop input.rbxmx ClassName PropertyName --output output.rbxm +``` diff --git a/rbx_util/src/main.rs b/rbx_util/src/main.rs index 0410527e0..7314936f5 100644 --- a/rbx_util/src/main.rs +++ b/rbx_util/src/main.rs @@ -1,16 +1,18 @@ mod convert; +mod remove_prop; mod view_binary; use std::process; use std::{path::Path, str::FromStr}; use clap::Parser; -use convert::ConvertCommand; +use convert::ConvertCommand; +use remove_prop::RemovePropCommand; use view_binary::ViewBinaryCommand; #[derive(Debug, Parser)] -#[clap(name = "rbx_util", about)] +#[clap(name = "rbx_util", about, version)] struct Options { #[clap(flatten)] global: GlobalOptions, @@ -23,6 +25,7 @@ impl Options { match self.subcommand { Subcommand::ViewBinary(command) => command.run(), Subcommand::Convert(command) => command.run(), + Subcommand::RemoveProp(command) => command.run(), } } } @@ -33,6 +36,8 @@ enum Subcommand { ViewBinary(ViewBinaryCommand), /// Convert between the XML and binary formats for places and models. Convert(ConvertCommand), + /// Removes a specific property from a specific class within a Roblox file. + RemoveProp(RemovePropCommand), } #[derive(Debug, Parser, Clone, Copy)] diff --git a/rbx_util/src/remove_prop.rs b/rbx_util/src/remove_prop.rs new file mode 100644 index 000000000..ad46cae9c --- /dev/null +++ b/rbx_util/src/remove_prop.rs @@ -0,0 +1,80 @@ +use std::{ + io::{BufReader, BufWriter}, + path::PathBuf, +}; + +use anyhow::Context as _; +use clap::Parser; +use fs_err::File; + +use crate::ModelKind; + +#[derive(Debug, Parser)] +pub struct RemovePropCommand { + /// The file to remove the property from. + input: PathBuf, + #[clap(long, short)] + /// The place to write the stripped file to. + output: PathBuf, + /// The class name to remove the property from. + class_name: String, + /// The property to remove from the provided class. + prop_name: String, +} + +impl RemovePropCommand { + pub fn run(&self) -> anyhow::Result<()> { + let input_kind = ModelKind::from_path(&self.input)?; + let output_kind = ModelKind::from_path(&self.output)?; + + let input_file = BufReader::new(File::open(&self.input)?); + + log::debug!("Reading from {input_kind:?} file {}", self.input.display()); + let mut dom = match input_kind { + ModelKind::Xml => { + let options = rbx_xml::DecodeOptions::new() + .property_behavior(rbx_xml::DecodePropertyBehavior::ReadUnknown); + + rbx_xml::from_reader(input_file, options) + .with_context(|| format!("Failed to read {}", self.input.display()))? + } + + ModelKind::Binary => rbx_binary::from_reader(input_file) + .with_context(|| format!("Failed to read {}", self.input.display()))?, + }; + + let mut queue = vec![dom.root_ref()]; + while let Some(referent) = queue.pop() { + let inst = dom.get_by_ref_mut(referent).unwrap(); + if inst.class == self.class_name { + log::trace!("Removed property {}.{}", inst.name, self.prop_name); + inst.properties.remove(&self.prop_name); + } + queue.extend_from_slice(inst.children()); + } + + let output_file = BufWriter::new(File::create(&self.output)?); + + let root_ids = dom.root().children(); + match output_kind { + ModelKind::Xml => { + let options = rbx_xml::EncodeOptions::new() + .property_behavior(rbx_xml::EncodePropertyBehavior::WriteUnknown); + + rbx_xml::to_writer(output_file, &dom, root_ids, options) + .with_context(|| format!("Failed to write {}", self.output.display()))?; + } + + ModelKind::Binary => { + rbx_binary::to_writer(output_file, &dom, root_ids) + .with_context(|| format!("Failed to write {}", self.output.display()))?; + } + } + log::info!( + "Wrote stripped {output_kind:?} file to {}", + self.output.display() + ); + + Ok(()) + } +}