use std::unimplemented; use clap::{Args, Parser, Subcommand}; #[derive(Parser)] #[command(author, version, about, long_about = None)] #[command(propagate_version = true)] struct Cli { #[command(subcommand)] command: Commands, } #[derive(Subcommand)] enum Commands { Download(MapList), Scan, Replace, Upload, } #[derive(Args)] struct MapList { maps: Vec, } fn class_is_a(class: &str, superclass: &str) -> bool { if class==superclass { return true } let class_descriptor=rbx_reflection_database::get().classes.get(class); if let Some(descriptor) = &class_descriptor { if let Some(class_super) = &descriptor.superclass { return class_is_a(&class_super, superclass) } } return false } //download //download list of maps to maps/unprocessed //scan (scripts) //iter maps/unprocessed //passing moves to maps/verified //failing moves to maps/purgatory //replace (edits & deletions) //iter maps/purgatory //replace scripts and put in maps/unprocessed //upload //iter maps/verified //interactively print DisplayName/Creator and ask for target upload ids fn get_scripts(dom:rbx_dom_weak::WeakDom) -> Vec{ let mut scripts = std::vec::Vec::::new(); let (_,mut instances) = dom.into_raw(); for (_,instance) in instances.drain() { if class_is_a(instance.class.as_str(), "LuaSourceContainer") { scripts.push(instance); } } scripts } fn download(map_list: Vec) -> Result<(), Box>{ let header=format!("Cookie: .ROBLOSECURITY={}",std::env::var("RBXCOOKIE")?); let shared_args=&[ "-q", "--header", header.as_str(), "-O", ]; for map_id in map_list.iter() { std::process::Command::new("wget") .args(shared_args) .arg(format!("maps/unprocessed/{}.rbxl",map_id)) .arg(format!("https://assetdelivery.roblox.com/v1/asset/?ID={}",map_id)) .spawn()?; } Ok(()) } enum Scan{ Passed, Blocked, Flagged, } fn scan() -> Result<(), Box>{ let mut id = 0u32; match std::fs::read_to_string("id"){ Ok(id_file)=>id=id_file.parse::()?, Err(e) => match e.kind() { std::io::ErrorKind::NotFound => println!("id file does not exist: starting from 0"),//continue on, implicitly take on id=0, write the id file at the end _ => return Err(e)?, } } //Construct allowed scripts let mut allowed_set = std::collections::HashSet::::new(); for entry in std::fs::read_dir("scripts/allowed")? { allowed_set.insert(std::fs::read_to_string(entry?.path())?); } let mut blocked = std::collections::HashSet::::new(); for entry in std::fs::read_dir("scripts/blocked")? { blocked.insert(std::fs::read_to_string(entry?.path())?); } for entry in std::fs::read_dir("maps/unprocessed")? { let file_thing=entry?; let input = std::io::BufReader::new(std::fs::File::open(file_thing.path())?); let dom = rbx_binary::from_reader(input)?; let scripts = get_scripts(dom); //check scribb let mut fail_count=0; let mut fail_type=Scan::Passed; for script in scripts.iter() { if let Some(rbx_dom_weak::types::Variant::String(s)) = script.properties.get("Source") { //flag keywords and instantly fail if s.find("getfenv").is_some()||s.find("require").is_some(){ println!("{:?} - flagged.",file_thing.file_name()); fail_type=Scan::Flagged; break; } if allowed_set.contains(s) { continue; }else{ fail_type=Scan::Blocked;//no need to check for Flagged, it breaks the loop. fail_count+=1; if !blocked.contains(s) { blocked.insert(s.clone());//all fixed! just clone! std::fs::write(format!("scripts/blocked/{}.lua",id),s)?; id+=1; } } }else{ panic!("FATAL: failed to get source for {:?}",file_thing.file_name()); } } let mut dest=match fail_type { Scan::Passed => std::path::PathBuf::from("maps/processed"), Scan::Blocked => { println!("{:?} - {} {} not allowed.",file_thing.file_name(),fail_count,if fail_count==1 {"script"}else{"scripts"}); std::path::PathBuf::from("maps/purgatory") } Scan::Flagged => std::path::PathBuf::from("maps/flagged") }; dest.push(file_thing.file_name()); std::fs::rename(file_thing.path(), dest)?; } std::fs::write("id",id.to_string())?; Ok(()) } fn replace() -> Result<(), Box>{ //Construct allowed scripts let mut allowed_map = std::collections::HashMap::::new(); for entry in std::fs::read_dir("scripts/allowed")? { let entry=entry?; allowed_map.insert(entry.file_name().to_str().unwrap().parse::()?,std::fs::read_to_string(entry.path())?); } let mut replace = std::collections::HashMap::::new(); for entry in std::fs::read_dir("scripts/replace")? { let entry=entry?; replace.insert(std::fs::read_to_string(entry.path())?,entry.file_name().to_str().unwrap().parse::()?); } for entry in std::fs::read_dir("maps/purgatory")? { let file_thing=entry?; let input = std::io::BufReader::new(std::fs::File::open(file_thing.path())?); let dom = rbx_binary::from_reader(input)?; let mut write_dom = rbx_dom_weak::WeakDom::new(rbx_dom_weak::InstanceBuilder::empty()); dom.clone_into_external(dom.root_ref(), &mut write_dom); let scripts = get_scripts(dom); //check scribb let mut any_failed=false; for script in scripts.iter() { if let Some(rbx_dom_weak::types::Variant::String(source)) = script.properties.get("Source") { if let (Some(replace_id),Some(replace_script))=(replace.get(source),write_dom.get_by_ref_mut(script.referent())) { println!("replace {}",replace_id); //replace the source if let Some(replace_source)=allowed_map.get(replace_id){ replace_script.properties.insert("Source".to_string(), rbx_dom_weak::types::Variant::String(replace_source.clone())); }else{ println!("failed to get replacement source {}",replace_id); any_failed=true; } } } } if any_failed { println!("One or more scripts failed to replace."); }else{ let mut dest=std::path::PathBuf::from("maps/unprocessed"); dest.set_file_name(file_thing.file_name()); dest.set_extension("rbxl");//extension is always rbxl even if source file is extensionless let output = std::io::BufWriter::new(std::fs::File::open(dest)?); rbx_binary::to_writer(output, &write_dom, &[write_dom.root_ref()])?; } } Ok(()) } fn upload() -> Result<(), Box>{ // std::process::Command::new("rbxcompiler") // .arg("--compile=false") // .arg("--group=6980477") // .arg("--asset=5692139100") // .arg("--input=map.rbxm") // .spawn()?; unimplemented!() } fn main() -> Result<(), Box> { let cli = Cli::parse(); match cli.command { Commands::Download(map_list)=>download(map_list.maps), Commands::Scan=>scan(), Commands::Replace=>replace(), Commands::Upload=>upload(), } }