From 327688d79ee308fed6346b0d86f9727b7844fe08 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Fri, 18 Apr 2025 10:42:20 -0700 Subject: [PATCH] rbx-dom: guard rail ustr footguns --- lib/rbx_loader/src/loader.rs | 10 ++-- lib/rbx_loader/src/rbx.rs | 46 ++++++++++--------- lib/roblox_emulator/src/context.rs | 11 +++-- .../src/runner/instance/instance.rs | 22 ++++++--- map-tool/src/roblox.rs | 15 ++++-- 5 files changed, 65 insertions(+), 39 deletions(-) diff --git a/lib/rbx_loader/src/loader.rs b/lib/rbx_loader/src/loader.rs index 9593652b..9096c466 100644 --- a/lib/rbx_loader/src/loader.rs +++ b/lib/rbx_loader/src/loader.rs @@ -1,5 +1,4 @@ use std::io::Read; -use rbx_dom_weak::ustr; use rbxassetid::{RobloxAssetId,RobloxAssetIdParseErr}; use strafesnet_common::model::Mesh; use strafesnet_deferred_loader::{loader::Loader,texture::Texture}; @@ -7,6 +6,11 @@ use strafesnet_deferred_loader::{loader::Loader,texture::Texture}; use crate::data::RobloxMeshBytes; use crate::rbx::RobloxPartDescription; +// disallow non-static lifetimes +fn static_ustr(s:&'static str)->rbx_dom_weak::Ustr{ + rbx_dom_weak::ustr(s) +} + fn read_entire_file(path:impl AsRef)->Result,std::io::Error>{ let mut file=std::fs::File::open(path)?; let mut data=Vec::new(); @@ -172,12 +176,12 @@ impl Loader for MeshLoader{ return Err(MeshError::MissingInstance); }; if physics_data.is_empty(){ - if let Some(rbx_dom_weak::types::Variant::BinaryString(data))=instance.properties.get(&ustr("PhysicsData")){ + if let Some(rbx_dom_weak::types::Variant::BinaryString(data))=instance.properties.get(&static_ustr("PhysicsData")){ physics_data=data.as_ref(); } } if mesh_data.is_empty(){ - if let Some(rbx_dom_weak::types::Variant::BinaryString(data))=instance.properties.get(&ustr("MeshData")){ + if let Some(rbx_dom_weak::types::Variant::BinaryString(data))=instance.properties.get(&static_ustr("MeshData")){ mesh_data=data.as_ref(); } } diff --git a/lib/rbx_loader/src/rbx.rs b/lib/rbx_loader/src/rbx.rs index 954edae9..15cbf2e0 100644 --- a/lib/rbx_loader/src/rbx.rs +++ b/lib/rbx_loader/src/rbx.rs @@ -1,7 +1,6 @@ use std::collections::HashMap; use crate::loader::MeshIndex; use crate::primitives::{self,CubeFace,CubeFaceDescription,WedgeFaceDescription,CornerWedgeFaceDescription,FaceDescription,Primitives}; -use rbx_dom_weak::ustr; use strafesnet_common::aabb::Aabb; use strafesnet_common::map; use strafesnet_common::model; @@ -14,6 +13,11 @@ use strafesnet_deferred_loader::deferred_loader::{RenderConfigDeferredLoader,Mes use strafesnet_deferred_loader::mesh::Meshes; use strafesnet_deferred_loader::texture::{RenderConfigs,Texture}; +// disallow non-static lifetimes +fn static_ustr(s:&'static str)->rbx_dom_weak::Ustr{ + rbx_dom_weak::ustr(s) +} + fn class_is_a(class: &str, superclass: &str) -> bool { if class==superclass { return true @@ -424,10 +428,10 @@ fn get_texture_description<'a>( Some(rbx_dom_weak::types::Variant::Color3(decal_color3)), Some(rbx_dom_weak::types::Variant::Float32(decal_transparency)), )=( - decal.properties.get(&ustr("Texture")), - decal.properties.get(&ustr("Face")), - decal.properties.get(&ustr("Color3")), - decal.properties.get(&ustr("Transparency")), + decal.properties.get(&static_ustr("Texture")), + decal.properties.get(&static_ustr("Face")), + decal.properties.get(&static_ustr("Color3")), + decal.properties.get(&static_ustr("Transparency")), )else{ println!("Decal is missing a required property"); continue; @@ -446,10 +450,10 @@ fn get_texture_description<'a>( Some(&rbx_dom_weak::types::Variant::Float32(studs_per_tile_u)), Some(&rbx_dom_weak::types::Variant::Float32(studs_per_tile_v)), ) = ( - decal.properties.get(&ustr("OffsetStudsU")), - decal.properties.get(&ustr("OffsetStudsV")), - decal.properties.get(&ustr("StudsPerTileU")), - decal.properties.get(&ustr("StudsPerTileV")), + decal.properties.get(&static_ustr("OffsetStudsU")), + decal.properties.get(&static_ustr("OffsetStudsV")), + decal.properties.get(&static_ustr("StudsPerTileU")), + decal.properties.get(&static_ustr("StudsPerTileV")), ) { let (size_u,size_v)=match cube_face{ @@ -547,12 +551,12 @@ pub fn convert<'a>( Some(rbx_dom_weak::types::Variant::Color3uint8(color3)), Some(rbx_dom_weak::types::Variant::Bool(can_collide)), ) = ( - object.properties.get(&ustr("CFrame")), - object.properties.get(&ustr("Size")), - object.properties.get(&ustr("Velocity")), - object.properties.get(&ustr("Transparency")), - object.properties.get(&ustr("Color")), - object.properties.get(&ustr("CanCollide")), + object.properties.get(&static_ustr("CFrame")), + object.properties.get(&static_ustr("Size")), + object.properties.get(&static_ustr("Velocity")), + object.properties.get(&static_ustr("Transparency")), + object.properties.get(&static_ustr("Color")), + object.properties.get(&static_ustr("CanCollide")), ) { let model_transform=planar64_affine3_from_roblox(cf,size); @@ -571,7 +575,7 @@ pub fn convert<'a>( //TODO: also detect "CylinderMesh" etc here let shape=match object.class.as_str(){ - "Part"=>if let Some(rbx_dom_weak::types::Variant::Enum(shape))=object.properties.get(&ustr("Shape")){ + "Part"=>if let Some(rbx_dom_weak::types::Variant::Enum(shape))=object.properties.get(&static_ustr("Shape")){ Shape::Primitive(shape.to_u32().try_into().expect("Funky roblox PartType")) }else{ panic!("Part has no Shape!"); @@ -645,9 +649,9 @@ pub fn convert<'a>( Some(rbx_dom_weak::types::Variant::Content(texture_content)), )=( // mesh must exist - object.properties.get(&ustr("MeshContent")), + object.properties.get(&static_ustr("MeshContent")), // texture is allowed to be none - object.properties.get(&ustr("TextureContent")), + object.properties.get(&static_ustr("TextureContent")), ){ let mesh_asset_id=get_content_url(mesh_content).expect("MeshPart Mesh is not a Uri"); let texture_asset_id=get_content_url(texture_content); @@ -662,13 +666,13 @@ pub fn convert<'a>( let mut content=""; let mut mesh_data:&[u8]=&[]; let mut physics_data:&[u8]=&[]; - if let Some(rbx_dom_weak::types::Variant::ContentId(asset_id))=object.properties.get(&ustr("AssetId")){ + if let Some(rbx_dom_weak::types::Variant::ContentId(asset_id))=object.properties.get(&static_ustr("AssetId")){ content=asset_id.as_ref(); } - if let Some(rbx_dom_weak::types::Variant::BinaryString(data))=object.properties.get(&ustr("MeshData")){ + if let Some(rbx_dom_weak::types::Variant::BinaryString(data))=object.properties.get(&static_ustr("MeshData")){ mesh_data=data.as_ref(); } - if let Some(rbx_dom_weak::types::Variant::BinaryString(data))=object.properties.get(&ustr("PhysicsData")){ + if let Some(rbx_dom_weak::types::Variant::BinaryString(data))=object.properties.get(&static_ustr("PhysicsData")){ physics_data=data.as_ref(); } let part_texture_description=get_texture_description(&mut temp_objects,render_config_deferred_loader,dom,object,size); diff --git a/lib/roblox_emulator/src/context.rs b/lib/roblox_emulator/src/context.rs index 59ade4a6..7dd360ba 100644 --- a/lib/roblox_emulator/src/context.rs +++ b/lib/roblox_emulator/src/context.rs @@ -1,4 +1,9 @@ -use rbx_dom_weak::{types::Ref,ustr,InstanceBuilder,WeakDom}; +use rbx_dom_weak::{types::Ref,InstanceBuilder,WeakDom}; + +// disallow non-static lifetimes +fn static_ustr(s:&'static str)->rbx_dom_weak::Ustr{ + rbx_dom_weak::ustr(s) +} pub fn class_is_a(class:&str,superclass:&str)->bool{ class==superclass @@ -70,8 +75,8 @@ impl Context{ { //Lowercase and upper case workspace property! let game=self.dom.root_mut(); - game.properties.insert(ustr("workspace"),rbx_types::Variant::Ref(workspace)); - game.properties.insert(ustr("Workspace"),rbx_types::Variant::Ref(workspace)); + game.properties.insert(static_ustr("workspace"),rbx_types::Variant::Ref(workspace)); + game.properties.insert(static_ustr("Workspace"),rbx_types::Variant::Ref(workspace)); } self.dom.insert(game,InstanceBuilder::new("Lighting")); diff --git a/lib/roblox_emulator/src/runner/instance/instance.rs b/lib/roblox_emulator/src/runner/instance/instance.rs index d8bf57ca..4400a2ad 100644 --- a/lib/roblox_emulator/src/runner/instance/instance.rs +++ b/lib/roblox_emulator/src/runner/instance/instance.rs @@ -2,10 +2,15 @@ use std::collections::{hash_map::Entry,HashMap}; use mlua::{FromLua,FromLuaMulti,IntoLua,IntoLuaMulti}; use rbx_types::Ref; -use rbx_dom_weak::{ustr,Ustr,InstanceBuilder,WeakDom}; +use rbx_dom_weak::{Ustr,InstanceBuilder,WeakDom}; use crate::runner::vector3::Vector3; +// disallow non-static lifetimes +fn static_ustr(s:&'static str)->rbx_dom_weak::Ustr{ + rbx_dom_weak::ustr(s) +} + pub fn set_globals(lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{ //class functions store lua.set_app_data(ClassMethodsStore::default()); @@ -58,7 +63,7 @@ fn get_full_name(dom:&rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance)->S pub fn get_name_source(lua:&mlua::Lua,script:Instance)->Result<(String,String),mlua::Error>{ dom_mut(lua,|dom|{ let instance=script.get(dom)?; - let source=match instance.properties.get(&ustr("Source")){ + let source=match instance.properties.get(&static_ustr("Source")){ Some(rbx_dom_weak::types::Variant::String(s))=>s.clone(), _=>Err(mlua::Error::external("Missing script.Source"))?, }; @@ -208,7 +213,9 @@ impl mlua::UserData for Instance{ }); methods.add_meta_function(mlua::MetaMethod::Index,|lua,(this,index):(Instance,mlua::String)|{ let index_str=&*index.to_str()?; - let index_ustr=ustr(index_str); + let Some(index_ustr)=Ustr::from_existing(index_str)else{ + return Ok(mlua::Value::Nil) + }; dom_mut(lua,|dom|{ let instance=this.get(dom)?; //println!("__index t={} i={index:?}",instance.name); @@ -262,11 +269,12 @@ impl mlua::UserData for Instance{ }) }); methods.add_meta_function(mlua::MetaMethod::NewIndex,|lua,(this,index,value):(Instance,mlua::String,mlua::Value)|{ + let index_str=&*index.to_str()?; + let Some(index_ustr)=Ustr::from_existing(index_str)else{ + return Ok(()) + }; dom_mut(lua,|dom|{ let instance=this.get_mut(dom)?; - //println!("__newindex t={} i={index:?} v={value:?}",instance.name); - let index_str=&*index.to_str()?; - let index_ustr=ustr(index_str); let db=rbx_reflection_database::get(); let class=db.classes.get(instance.class.as_str()).ok_or_else(||mlua::Error::runtime("Class missing"))?; let property=db.superclasses_iter(class).find_map(|cls| @@ -462,7 +470,7 @@ fn find_virtual_property( let virtual_property=class_virtual_properties.get(index)?; //Get source property - let variant=properties.get(&ustr(virtual_property.property))?; + let variant=properties.get(&static_ustr(virtual_property.property))?; //Transform Source property with provided function (virtual_property.pointer)(variant) diff --git a/map-tool/src/roblox.rs b/map-tool/src/roblox.rs index d09285d6..50fa1bba 100644 --- a/map-tool/src/roblox.rs +++ b/map-tool/src/roblox.rs @@ -3,11 +3,16 @@ use std::io::{Cursor,Read,Seek}; use std::collections::HashSet; use clap::{Args,Subcommand}; use anyhow::Result as AResult; -use rbx_dom_weak::{ustr,Instance}; +use rbx_dom_weak::Instance; use strafesnet_deferred_loader::deferred_loader::LoadFailureMode; use rbxassetid::RobloxAssetId; use tokio::io::AsyncReadExt; +// disallow non-static lifetimes +fn static_ustr(s:&'static str)->rbx_dom_weak::Ustr{ + rbx_dom_weak::ustr(s) +} + const DOWNLOAD_LIMIT:usize=16; #[derive(Subcommand)] @@ -102,8 +107,8 @@ SurfaceAppearance.RoughnessMapContent WrapLayer.ReferenceMeshContent */ -fn accumulate_content(content_list:&mut HashSet,object:&Instance,property:&str){ - let Some(rbx_dom_weak::types::Variant::Content(content))=object.properties.get(&ustr(property))else{ +fn accumulate_content(content_list:&mut HashSet,object:&Instance,property:&'static str){ + let Some(rbx_dom_weak::types::Variant::Content(content))=object.properties.get(&static_ustr(property))else{ println!("property={} does not exist for class={}",property,object.class.as_str()); return; }; @@ -117,8 +122,8 @@ fn accumulate_content(content_list:&mut HashSet,object:&Instance, }; content_list.insert(asset_id); } -fn accumulate_content_id(content_list:&mut HashSet,object:&Instance,property:&str){ - let Some(rbx_dom_weak::types::Variant::ContentId(content))=object.properties.get(&ustr(property))else{ +fn accumulate_content_id(content_list:&mut HashSet,object:&Instance,property:&'static str){ + let Some(rbx_dom_weak::types::Variant::ContentId(content))=object.properties.get(&static_ustr(property))else{ println!("property={} does not exist for class={}",property,object.class.as_str()); return; };