Compare commits

...

5 Commits

Author SHA1 Message Date
479dd37f53 v1.1.0 download-textures 2023-09-22 14:27:06 -07:00
34b6a869f0 add download textures command 2023-09-22 14:24:28 -07:00
19a455ee5e print wget exit status 2023-09-22 14:23:29 -07:00
9904b7a044 switch extract to pathbuf 2023-09-22 14:22:44 -07:00
6efa811eb6 generalize recursive_collect 2023-09-22 14:21:22 -07:00
3 changed files with 163 additions and 24 deletions

47
Cargo.lock generated

@ -2,6 +2,15 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab"
dependencies = [
"memchr",
]
[[package]]
name = "anstream"
version = "0.5.0"
@ -264,14 +273,21 @@ dependencies = [
[[package]]
name = "map-tool"
version = "1.0.0"
version = "1.1.0"
dependencies = [
"clap",
"rbx_binary",
"rbx_dom_weak",
"rbx_reflection_database",
"regex",
]
[[package]]
name = "memchr"
version = "2.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c"
[[package]]
name = "num-traits"
version = "0.2.16"
@ -423,6 +439,35 @@ dependencies = [
"thiserror",
]
[[package]]
name = "regex"
version = "1.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
[[package]]
name = "rmp"
version = "0.8.12"

@ -1,6 +1,6 @@
[package]
name = "map-tool"
version = "1.0.0"
version = "1.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -10,6 +10,7 @@ clap = { version = "4.4.2", features = ["derive"] }
rbx_binary = "0.7.1"
rbx_dom_weak = "2.5.0"
rbx_reflection_database = "0.2.7"
regex = "1.9.5"
[profile.release]
lto = true

@ -19,16 +19,17 @@ struct Cli {
#[derive(Subcommand)]
enum Commands {
Download(MapList),
DownloadTextures(PathBufList),
Upload,
Scan,
Extract(Map),
Extract(PathBufList),
Replace,
Interactive,
}
#[derive(Args)]
struct Map {
id:u64,
struct PathBufList {
paths:Vec<std::path::PathBuf>
}
#[derive(Args)]
@ -48,13 +49,13 @@ fn class_is_a(class: &str, superclass: &str) -> bool {
}
return false
}
fn recursive_collect_scripts(scripts: &mut std::vec::Vec<rbx_dom_weak::types::Ref>,dom: &rbx_dom_weak::WeakDom, instance: &rbx_dom_weak::Instance){
fn recursive_collect_superclass(objects: &mut std::vec::Vec<rbx_dom_weak::types::Ref>,dom: &rbx_dom_weak::WeakDom, instance: &rbx_dom_weak::Instance, superclass: &str){
for &referent in instance.children() {
if let Some(c) = dom.get_by_ref(referent) {
if class_is_a(c.class.as_str(), "LuaSourceContainer") {
scripts.push(c.referent());//copy ref
if class_is_a(c.class.as_str(), superclass) {
objects.push(c.referent());//copy ref
}
recursive_collect_scripts(scripts,dom,c);
recursive_collect_superclass(objects,dom,c,superclass);
}
}
}
@ -89,9 +90,17 @@ fn get_full_name(dom:&rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance) ->
fn get_script_refs(dom:&rbx_dom_weak::WeakDom) -> Vec<rbx_dom_weak::types::Ref>{
let mut scripts = std::vec::Vec::new();
recursive_collect_scripts(&mut scripts, dom, dom.root());
recursive_collect_superclass(&mut scripts, dom, dom.root(),"LuaSourceContainer");
scripts
}
fn get_texture_refs(dom:&rbx_dom_weak::WeakDom) -> Vec<rbx_dom_weak::types::Ref>{
let mut objects = std::vec::Vec::new();
recursive_collect_superclass(&mut objects, dom, dom.root(),"Decal");
//get ids
//clear vec
//next class
objects
}
fn get_id() -> BoxResult<u32>{
match std::fs::read_to_string("id"){
@ -189,8 +198,95 @@ fn download(map_list: Vec<u64>) -> BoxResult<()>{
.spawn()
}).collect();
//naively wait for all because idk how to make an async progress bar lmao
for mut child in processes_result?{
child.wait()?;
for child in processes_result?{
let output=child.wait_with_output()?;
println!("map exit_success:{}",output.status.success());
}
Ok(())
}
struct RobloxAssetId(u64);
struct RobloxAssetIdParseErr;
impl std::str::FromStr for RobloxAssetId {
type Err=RobloxAssetIdParseErr;
fn from_str(s: &str) -> Result<Self, Self::Err>{
let regman=regex::Regex::new(r"(\d+)$").unwrap();
if let Some(captures) = regman.captures(s) {
if captures.len()==2{//captures[0] is all captures concatenated, and then each individual capture
if let Ok(id) = captures[0].parse::<u64>() {
return Ok(Self(id));
}
}
}
Err(RobloxAssetIdParseErr)
}
}
/* The ones I'm interested in:
Beam.Texture
Decal.Texture
FileMesh.MeshId
FileMesh.TextureId
MaterialVariant.ColorMap
MaterialVariant.MetalnessMap
MaterialVariant.NormalMap
MaterialVariant.RoughnessMap
MeshPart.MeshId
MeshPart.TextureID
ParticleEmitter.Texture
Sky.MoonTextureId
Sky.SkyboxBk
Sky.SkyboxDn
Sky.SkyboxFt
Sky.SkyboxLf
Sky.SkyboxRt
Sky.SkyboxUp
Sky.SunTextureId
SurfaceAppearance.ColorMap
SurfaceAppearance.MetalnessMap
SurfaceAppearance.NormalMap
SurfaceAppearance.RoughnessMap
SurfaceAppearance.TexturePack
*/
fn download_textures(paths: Vec<std::path::PathBuf>) -> BoxResult<()>{
println!("download_textures paths:{:?}",paths);
let header=format!("Cookie: .ROBLOSECURITY={}",std::env::var("RBXCOOKIE")?);
let shared_args=&[
"-q",
"--header",
header.as_str(),
"-O",
];
let mut texture_list=std::collections::HashSet::new();
for path in paths {
let input = std::io::BufReader::new(std::fs::File::open(path)?);
let dom = rbx_binary::from_reader(input)?;
let object_refs = get_texture_refs(&dom);
for &object_ref in object_refs.iter() {
if let Some(object)=dom.get_by_ref(object_ref){
if let Some(rbx_dom_weak::types::Variant::Content(content)) = object.properties.get("Texture") {
println!("Texture content:{:?}",content);
if let Ok(asset_id)=content.clone().into_string().parse::<RobloxAssetId>(){
texture_list.insert(asset_id.0);
}
}
}
}
}
println!("Texture list:{:?}",texture_list);
let processes_result:Result<Vec<_>, _>=texture_list.iter().map(|asset_id|{
std::process::Command::new("wget")
.args(shared_args)
.arg(format!("textures/{}",asset_id))
.arg(format!("https://assetdelivery.roblox.com/v1/asset/?ID={}",asset_id))
.spawn()
}).collect();
//naively wait for all because idk how to make an async progress bar lmao
for child in processes_result?{
let output=child.wait_with_output()?;
println!("texture exit_success:{}",output.status.success());
}
Ok(())
}
@ -259,19 +355,15 @@ fn scan() -> BoxResult<()>{
std::fs::write("id",id.to_string())?;
Ok(())
}
fn extract(file_id:u64) -> BoxResult<()>{
fn extract(paths: Vec<std::path::PathBuf>) -> BoxResult<()>{
let mut id = 0;
//Construct allowed scripts
let mut script_set = std::collections::HashSet::<String>::new();
let file_id_string=file_id.to_string();
for entry in std::fs::read_dir("maps/unprocessed")? {
let file_thing=entry?;
if file_thing.file_name().to_str().unwrap().find(&file_id_string).is_none(){
continue;
}
let input = std::io::BufReader::new(std::fs::File::open(file_thing.path())?);
for path in paths {
let file_name=path.file_name();
let input = std::io::BufReader::new(std::fs::File::open(&path)?);
let dom = rbx_binary::from_reader(input)?;
@ -285,11 +377,11 @@ fn extract(file_id:u64) -> BoxResult<()>{
continue;
}else{
script_set.insert(s.clone());
std::fs::write(format!("scripts/extracted/{:?}_{}_{}.lua",file_thing.file_name(),id,script.name),s)?;
std::fs::write(format!("scripts/extracted/{:?}_{}_{}.lua",file_name,id,script.name),s)?;
id+=1;
}
}else{
panic!("FATAL: failed to get source for {:?}",file_thing.file_name());
panic!("FATAL: failed to get source for {:?}",file_name);
}
}else{
panic!("FATAL: failed to get_by_ref {:?}",script_ref);
@ -654,10 +746,11 @@ fn main() -> BoxResult<()> {
let cli = Cli::parse();
match cli.command {
Commands::Download(map_list)=>download(map_list.maps),
Commands::DownloadTextures(pathlist)=>download_textures(pathlist.paths),
Commands::Upload=>upload(),
Commands::Scan=>scan(),
Commands::Replace=>replace(),
Commands::Interactive=>interactive(),
Commands::Extract(map)=>extract(map.id),
Commands::Extract(pathlist)=>extract(pathlist.paths),
}
}