forked from StrafesNET/asset-tool
level up
This commit is contained in:
parent
48c2a010d8
commit
dba7aa427f
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -118,7 +118,6 @@ dependencies = [
|
||||
"git2",
|
||||
"lazy-regex",
|
||||
"pollster",
|
||||
"rayon",
|
||||
"rbx_asset",
|
||||
"rbx_binary",
|
||||
"rbx_dom_weak",
|
||||
@ -1358,6 +1357,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"lazy-regex",
|
||||
"rayon",
|
||||
"rbx_dom_weak",
|
||||
"rbx_xml",
|
||||
"tokio",
|
||||
|
@ -13,7 +13,6 @@ futures = "0.3.30"
|
||||
git2 = "0.18.1"
|
||||
lazy-regex = "3.1.0"
|
||||
pollster = "0.3.0"
|
||||
rayon = "1.8.0"
|
||||
rbx_asset = { path = "rbx_asset" }
|
||||
rbx_binary = "0.7.4"
|
||||
rbx_dom_weak = "2.7.0"
|
||||
|
@ -6,6 +6,7 @@ edition = "2021"
|
||||
[dependencies]
|
||||
futures = "0.3.30"
|
||||
lazy-regex = "3.1.0"
|
||||
rayon = "1.8.0"
|
||||
rbx_dom_weak = "2.7.0"
|
||||
rbx_xml = "0.13.3"
|
||||
tokio = { version = "1.35.1", features = ["fs"] }
|
||||
|
@ -1,7 +1,8 @@
|
||||
use std::path::PathBuf;
|
||||
use futures::{StreamExt, TryStreamExt};
|
||||
use tokio::io::AsyncReadExt;
|
||||
|
||||
use crate::types::{DecompileStyle,PropertiesOverride};
|
||||
use crate::types::{Style,PropertiesOverride};
|
||||
use crate::common::sanitize;
|
||||
|
||||
//holy smokes what am I doing lmao
|
||||
@ -235,7 +236,7 @@ enum CompileNodeError{
|
||||
FileName(std::ffi::OsString),
|
||||
ExtensionNotSupportedInStyle{
|
||||
extension:String,
|
||||
style:Option<DecompileStyle>,
|
||||
style:Option<Style>,
|
||||
},
|
||||
NoExtension,
|
||||
}
|
||||
@ -279,15 +280,15 @@ impl CompileNode{
|
||||
})
|
||||
}
|
||||
|
||||
async fn from_folder(entry:&tokio::fs::DirEntry,style:Option<DecompileStyle>)->Result<Self,CompileNodeError>{
|
||||
async fn from_folder(entry:&tokio::fs::DirEntry,style:Option<Style>)->Result<Self,CompileNodeError>{
|
||||
let contents_folder=entry.path();
|
||||
let file_name=entry.file_name();
|
||||
let search_name=file_name.to_str().unwrap();
|
||||
//scan inside the folder for an object to define the class of the folder
|
||||
let script_query=async {match style{
|
||||
Some(DecompileStyle::Rox)=>QuerySingle::rox(&contents_folder,search_name).resolve().await,
|
||||
Some(DecompileStyle::RoxRojo)=>QueryQuad::rox_rojo(&contents_folder,search_name).resolve().await,
|
||||
Some(DecompileStyle::Rojo)=>QueryTriple::rojo(&contents_folder).resolve().await,
|
||||
Some(Style::Rox)=>QuerySingle::rox(&contents_folder,search_name).resolve().await,
|
||||
Some(Style::RoxRojo)=>QueryQuad::rox_rojo(&contents_folder,search_name).resolve().await,
|
||||
Some(Style::Rojo)=>QueryTriple::rojo(&contents_folder).resolve().await,
|
||||
//try all three and complain if there is ambiguity
|
||||
None=>mega_triple_join(tokio::join!(
|
||||
QuerySingle::rox(&contents_folder,search_name).resolve(),
|
||||
@ -315,14 +316,14 @@ impl CompileNode{
|
||||
})
|
||||
}
|
||||
|
||||
async fn from_file(entry:&tokio::fs::DirEntry,style:Option<DecompileStyle>)->Result<Self,CompileNodeError>{
|
||||
async fn from_file(entry:&tokio::fs::DirEntry,style:Option<Style>)->Result<Self,CompileNodeError>{
|
||||
let mut file_name=entry
|
||||
.file_name()
|
||||
.into_string()
|
||||
.map_err(CompileNodeError::FileName)?;
|
||||
//reject goobers
|
||||
let is_goober=match style{
|
||||
Some(DecompileStyle::Rojo)=>true,
|
||||
Some(Style::Rojo)=>true,
|
||||
_=>false,
|
||||
};
|
||||
let (ext_len,file_discernment)={
|
||||
@ -394,11 +395,20 @@ enum TooComplicated<T>{
|
||||
Skip,
|
||||
}
|
||||
|
||||
enum CompileError{
|
||||
NullChildRef,
|
||||
pub struct CompileConfig{
|
||||
input_folder:PathBuf,
|
||||
style:Option<Style>,
|
||||
}
|
||||
|
||||
async fn compile(config:CompileConfig,&mut dom:rbx_dom_weak::WeakDom)->Result<(),CompileError>{
|
||||
enum CompileError{
|
||||
NullChildRef,
|
||||
IO(std::io::Error),
|
||||
CompileNode(CompileNodeError),
|
||||
DecodeError(rbx_xml::DecodeError),
|
||||
JoinError(tokio::task::JoinError),
|
||||
}
|
||||
|
||||
async fn compile(config:CompileConfig,mut dom:&mut rbx_dom_weak::WeakDom)->Result<(),CompileError>{
|
||||
//add in scripts and models
|
||||
let mut folder=config.input_folder.clone();
|
||||
let mut stack:Vec<CompileStackInstruction>=vec![CompileStackInstruction::TraverseReferent(dom.root_ref(),None)];
|
||||
@ -446,14 +456,14 @@ async fn compile(config:CompileConfig,&mut dom:rbx_dom_weak::WeakDom)->Result<()
|
||||
}else{
|
||||
TooComplicated::Stop
|
||||
};
|
||||
Ok::<_,anyhow::Error>(ret2)
|
||||
Ok::<_,std::io::Error>(ret2)
|
||||
})().await
|
||||
};
|
||||
match ret1{
|
||||
Ok(TooComplicated::Stop)=>None,
|
||||
Ok(TooComplicated::Skip)=>Some((Ok(None),dir1)),
|
||||
Ok(TooComplicated::Value(v))=>Some((Ok(Some(v)),dir1)),
|
||||
Err(e)=>Some((Err(e),dir1)),
|
||||
Err(e)=>Some((Err(CompileError::IO(e)),dir1)),
|
||||
}
|
||||
})
|
||||
|
||||
@ -461,21 +471,21 @@ async fn compile(config:CompileConfig,&mut dom:rbx_dom_weak::WeakDom)->Result<()
|
||||
.then(|bog|async{
|
||||
match bog{
|
||||
Ok(Some(entry))=>tokio::spawn(async move{
|
||||
let met=entry.metadata().await?;
|
||||
let met=entry.metadata().await.map_err(CompileError::IO)?;
|
||||
//discern that bad boy
|
||||
let compile_class=match met.is_dir(){
|
||||
true=>CompileNode::from_folder(&entry,style).await?,
|
||||
false=>CompileNode::from_file(&entry,style).await?,
|
||||
};
|
||||
true=>CompileNode::from_folder(&entry,style).await,
|
||||
false=>CompileNode::from_file(&entry,style).await,
|
||||
}.map_err(CompileError::CompileNode)?;
|
||||
//prepare data structure
|
||||
Ok(Some((compile_class.blacklist,match compile_class.class{
|
||||
CompileClass::Folder=>PreparedData::Builder(rbx_dom_weak::InstanceBuilder::new("Folder").with_name(compile_class.name.as_str())),
|
||||
CompileClass::Script(source)=>PreparedData::Builder(script_builder("Script",compile_class.name.as_str(),source)),
|
||||
CompileClass::LocalScript(source)=>PreparedData::Builder(script_builder("LocalScript",compile_class.name.as_str(),source)),
|
||||
CompileClass::ModuleScript(source)=>PreparedData::Builder(script_builder("ModuleScript",compile_class.name.as_str(),source)),
|
||||
CompileClass::Model(buf)=>PreparedData::Model(rbx_xml::from_reader_default(std::io::Cursor::new(buf))?),
|
||||
CompileClass::Model(buf)=>PreparedData::Model(rbx_xml::from_reader_default(std::io::Cursor::new(buf)).map_err(CompileError::DecodeError)?),
|
||||
})))
|
||||
}).await?,
|
||||
}).await.map_err(CompileError::JoinError)?,
|
||||
Ok(None)=>Ok(None),
|
||||
Err(e)=>Err(e),
|
||||
}
|
||||
@ -485,10 +495,10 @@ async fn compile(config:CompileConfig,&mut dom:rbx_dom_weak::WeakDom)->Result<()
|
||||
.map(|f|async{f}).buffer_unordered(32)
|
||||
|
||||
//begin processing immediately
|
||||
.fold((&mut stack,&mut dom),|(stack,dom),bog:Result<_,anyhow::Error>|async{
|
||||
.try_fold((&mut stack,&mut dom),|(stack,dom):(&mut Vec<CompileStackInstruction>,_),bog|async{
|
||||
//push child objects onto dom serially as they arrive
|
||||
match bog{
|
||||
Ok(Some((blacklist,data)))=>{
|
||||
Some((blacklist,data))=>{
|
||||
let referent=match data{
|
||||
PreparedData::Model(mut model_dom)=>{
|
||||
let referent=model_dom.root().children()[0];
|
||||
@ -500,14 +510,14 @@ async fn compile(config:CompileConfig,&mut dom:rbx_dom_weak::WeakDom)->Result<()
|
||||
//new children need to be traversed
|
||||
stack.push(CompileStackInstruction::TraverseReferent(referent,blacklist));
|
||||
},
|
||||
Ok(None)=>(),
|
||||
Err(e)=>println!("error lole {e:?}"),
|
||||
None=>(),
|
||||
}
|
||||
(stack,dom)
|
||||
}).await;
|
||||
Ok((stack,dom))
|
||||
}).await?;
|
||||
}
|
||||
},
|
||||
CompileStackInstruction::PopFolder=>assert!(folder.pop(),"pop folder bad"),
|
||||
}
|
||||
}
|
||||
unreachable!();
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use std::{io::Read, path::PathBuf};
|
||||
use std::path::PathBuf;
|
||||
use rbx_dom_weak::types::Ref;
|
||||
use crate::{common::sanitize, types::{DecompileStyle, PropertiesOverride}};
|
||||
use crate::{common::sanitize, types::{Style, PropertiesOverride}};
|
||||
|
||||
#[derive(PartialEq)]
|
||||
enum Class{
|
||||
@ -43,7 +43,13 @@ enum WriteStackInstruction<'a>{
|
||||
Destroy(Ref),
|
||||
}
|
||||
|
||||
fn write_item(dom:&rbx_dom_weak::WeakDom,mut file:PathBuf,node:&TreeNode,node_name_override:String,mut properties:PropertiesOverride,style:DecompileStyle,write_models:bool,write_scripts:bool)->AResult<()>{
|
||||
enum WriteError{
|
||||
ClassNotScript(String),
|
||||
IO(std::io::Error),
|
||||
EncodeError(rbx_xml::EncodeError),
|
||||
}
|
||||
|
||||
fn write_item(dom:&rbx_dom_weak::WeakDom,mut file:PathBuf,node:&TreeNode,node_name_override:String,mut properties:PropertiesOverride,style:Style,write_models:bool,write_scripts:bool)->Result<(),WriteError>{
|
||||
file.push(sanitize(node_name_override.as_str()).as_ref());
|
||||
match node.class{
|
||||
Class::Folder=>(),
|
||||
@ -54,8 +60,8 @@ fn write_item(dom:&rbx_dom_weak::WeakDom,mut file:PathBuf,node:&TreeNode,node_na
|
||||
|
||||
//set extension
|
||||
match style{
|
||||
DecompileStyle::Rox=>assert!(file.set_extension("lua"),"could not set extension"),
|
||||
DecompileStyle::RoxRojo|DecompileStyle::Rojo=>{
|
||||
Style::Rox=>assert!(file.set_extension("lua"),"could not set extension"),
|
||||
Style::RoxRojo|Style::Rojo=>{
|
||||
match properties.class.as_deref(){
|
||||
Some("LocalScript")=>{
|
||||
file.set_extension("client.lua");
|
||||
@ -70,7 +76,7 @@ fn write_item(dom:&rbx_dom_weak::WeakDom,mut file:PathBuf,node:&TreeNode,node_na
|
||||
// properties.class=None;
|
||||
// },
|
||||
None=>assert!(file.set_extension("lua"),"could not set extension"),
|
||||
Some(other)=>Err(anyhow::Error::msg(format!("Attempt to write a {} as a script",other)))?,
|
||||
Some(other)=>Err(WriteError::ClassNotScript(other.to_owned()))?,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -81,9 +87,9 @@ fn write_item(dom:&rbx_dom_weak::WeakDom,mut file:PathBuf,node:&TreeNode,node_na
|
||||
if properties.is_some(){
|
||||
//rox style
|
||||
let source=properties.to_string()+source.as_str();
|
||||
std::fs::write(file,source)?;
|
||||
std::fs::write(file,source).map_err(WriteError::IO)?;
|
||||
}else{
|
||||
std::fs::write(file,source)?;
|
||||
std::fs::write(file,source).map_err(WriteError::IO)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -93,216 +99,218 @@ fn write_item(dom:&rbx_dom_weak::WeakDom,mut file:PathBuf,node:&TreeNode,node_na
|
||||
return Ok(())
|
||||
}
|
||||
assert!(file.set_extension("rbxmx"));
|
||||
let output=std::io::BufWriter::new(std::fs::File::create(file)?);
|
||||
rbx_xml::to_writer_default(output,dom,&[node.referent])?;
|
||||
let output=std::io::BufWriter::new(std::fs::File::create(file).map_err(WriteError::IO)?);
|
||||
rbx_xml::to_writer_default(output,dom,&[node.referent]).map_err(WriteError::EncodeError)?;
|
||||
},
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct DecompiledContext{
|
||||
dom:rbx_dom_weak::WeakDom,
|
||||
tree_refs:std::collections::HashMap<rbx_dom_weak::types::Ref,TreeNode>,
|
||||
}
|
||||
|
||||
fn generate_decompiled_context(dom:rbx_dom_weak::WeakDom)->Result<DecompiledContext,DecompileError>{
|
||||
let mut tree_refs=std::collections::HashMap::new();
|
||||
tree_refs.insert(dom.root_ref(),TreeNode::new(
|
||||
"src".to_owned(),
|
||||
dom.root_ref(),
|
||||
Ref::none(),
|
||||
Class::Folder
|
||||
));
|
||||
|
||||
//run rules
|
||||
let mut stack=vec![dom.root()];
|
||||
while let Some(item)=stack.pop(){
|
||||
let class=match item.class.as_str(){
|
||||
"ModuleScript"=>Class::ModuleScript,
|
||||
"LocalScript"=>Class::LocalScript,
|
||||
"Script"=>Class::Script,
|
||||
"Model"=>Class::Model,
|
||||
_=>Class::Folder,
|
||||
};
|
||||
let skip=match class{
|
||||
Class::Model=>true,
|
||||
_=>false,
|
||||
};
|
||||
if let Some(parent_node)=tree_refs.get_mut(&item.parent()){
|
||||
let referent=item.referent();
|
||||
let node=TreeNode::new(item.name.clone(),referent,parent_node.referent,class);
|
||||
parent_node.children.push(referent);
|
||||
tree_refs.insert(referent,node);
|
||||
}
|
||||
//look no further, turn this node and all its children into a model
|
||||
if skip{
|
||||
continue;
|
||||
}
|
||||
for &referent in item.children(){
|
||||
if let Some(c)=dom.get_by_ref(referent){
|
||||
stack.push(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//trim empty folders
|
||||
let mut script_count=0;
|
||||
let mut stack:Vec<TrimStackInstruction>=tree_refs.get(&dom.root_ref()).unwrap().children
|
||||
.iter().map(|&c|TrimStackInstruction::Referent(c)).collect();
|
||||
while let Some(instruction)=stack.pop(){
|
||||
match instruction{
|
||||
TrimStackInstruction::IncrementScript=>script_count+=1,
|
||||
TrimStackInstruction::DecrementScript=>script_count-=1,
|
||||
TrimStackInstruction::Referent(referent)=>{
|
||||
let mut delete=None;
|
||||
if let Some(node)=tree_refs.get_mut(&referent){
|
||||
if node.class==Class::Folder&&script_count!=0{
|
||||
node.class=Class::Model
|
||||
}
|
||||
if node.class==Class::Folder&&node.children.len()==0{
|
||||
delete=Some(node.parent);
|
||||
}else{
|
||||
//how the hell do I do this better without recursion
|
||||
let is_script=match node.class{
|
||||
Class::ModuleScript|Class::LocalScript|Class::Script=>true,
|
||||
_=>false,
|
||||
};
|
||||
//stack is popped from back
|
||||
if is_script{
|
||||
stack.push(TrimStackInstruction::DecrementScript);
|
||||
}
|
||||
for &child_referent in &node.children{
|
||||
stack.push(TrimStackInstruction::Referent(child_referent));
|
||||
}
|
||||
if is_script{
|
||||
stack.push(TrimStackInstruction::IncrementScript);
|
||||
}
|
||||
}
|
||||
}
|
||||
//trim referent
|
||||
if let Some(parent_ref)=delete{
|
||||
let parent_node=tree_refs.get_mut(&parent_ref)
|
||||
.expect("parent_ref does not exist in tree_refs");
|
||||
parent_node.children.remove(
|
||||
parent_node.children.iter()
|
||||
.position(|&r|r==referent)
|
||||
.expect("parent.children does not contain referent")
|
||||
);
|
||||
tree_refs.remove(&referent);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Ok(DecompiledContext{
|
||||
dom,
|
||||
tree_refs,
|
||||
})
|
||||
}
|
||||
|
||||
struct WriteConfig{
|
||||
style:DecompileStyle,
|
||||
pub struct WriteConfig{
|
||||
style:Style,
|
||||
output_folder:PathBuf,
|
||||
write_template:bool,
|
||||
write_models:bool,
|
||||
write_scripts:bool,
|
||||
}
|
||||
|
||||
async fn write_files(config:WriteConfig,mut context:DecompiledContext)->Result<(),WriteError>{
|
||||
let mut write_queue=Vec::new();
|
||||
let mut destroy_queue=Vec::new();
|
||||
pub struct DecompiledContext{
|
||||
dom:rbx_dom_weak::WeakDom,
|
||||
tree_refs:std::collections::HashMap<rbx_dom_weak::types::Ref,TreeNode>,
|
||||
}
|
||||
|
||||
let mut name_tally=std::collections::HashMap::<String,u32>::new();
|
||||
let mut folder=config.output_folder.clone();
|
||||
let mut stack=vec![WriteStackInstruction::Node(context.tree_refs.get(&context.dom.root_ref()).unwrap(),0)];
|
||||
while let Some(instruction)=stack.pop(){
|
||||
match instruction{
|
||||
WriteStackInstruction::PushFolder(component)=>folder.push(component),
|
||||
WriteStackInstruction::PopFolder=>assert!(folder.pop(),"weirdness"),
|
||||
WriteStackInstruction::Destroy(referent)=>destroy_queue.push(referent),
|
||||
WriteStackInstruction::Node(node,name_count)=>{
|
||||
//track properties that must be overriden to compile folder structure back into a place file
|
||||
let mut properties=PropertiesOverride::default();
|
||||
let has_children=node.children.len()!=0;
|
||||
match node.class{
|
||||
Class::Folder=>(),
|
||||
Class::ModuleScript=>(),//.lua files are ModuleScript by default
|
||||
Class::LocalScript=>properties.class=Some("LocalScript".to_owned()),
|
||||
Class::Script=>properties.class=Some("Script".to_owned()),
|
||||
Class::Model=>(),
|
||||
impl DecompiledContext{
|
||||
/// Will panic on circular tree structure but otherwise infallible
|
||||
pub fn from_dom(dom:rbx_dom_weak::WeakDom)->Self{
|
||||
let mut tree_refs=std::collections::HashMap::new();
|
||||
tree_refs.insert(dom.root_ref(),TreeNode::new(
|
||||
"src".to_owned(),
|
||||
dom.root_ref(),
|
||||
Ref::none(),
|
||||
Class::Folder
|
||||
));
|
||||
|
||||
//run rules
|
||||
let mut stack=vec![dom.root()];
|
||||
while let Some(item)=stack.pop(){
|
||||
let class=match item.class.as_str(){
|
||||
"ModuleScript"=>Class::ModuleScript,
|
||||
"LocalScript"=>Class::LocalScript,
|
||||
"Script"=>Class::Script,
|
||||
"Model"=>Class::Model,
|
||||
_=>Class::Folder,
|
||||
};
|
||||
let skip=match class{
|
||||
Class::Model=>true,
|
||||
_=>false,
|
||||
};
|
||||
if let Some(parent_node)=tree_refs.get_mut(&item.parent()){
|
||||
let referent=item.referent();
|
||||
let node=TreeNode::new(item.name.clone(),referent,parent_node.referent,class);
|
||||
parent_node.children.push(referent);
|
||||
tree_refs.insert(referent,node);
|
||||
}
|
||||
//look no further, turn this node and all its children into a model
|
||||
if skip{
|
||||
continue;
|
||||
}
|
||||
for &referent in item.children(){
|
||||
if let Some(c)=dom.get_by_ref(referent){
|
||||
stack.push(c);
|
||||
}
|
||||
let name_override=if 0<name_count{
|
||||
properties.name=Some(node.name.clone());
|
||||
format!("{}_{}",node.name,name_count)
|
||||
}else{
|
||||
node.name.clone()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if has_children{
|
||||
//push temp subfolder
|
||||
let mut subfolder=folder.clone();
|
||||
subfolder.push(sanitize(name_override.as_str()).as_ref());
|
||||
//make folder
|
||||
tokio::fs::create_dir(subfolder.clone()).await?;
|
||||
|
||||
let name_final=match config.style{
|
||||
DecompileStyle::Rox
|
||||
|DecompileStyle::RoxRojo=>name_override.clone(),
|
||||
DecompileStyle::Rojo=>"init".to_owned(),
|
||||
};
|
||||
|
||||
//write item in subfolder
|
||||
write_queue.push((subfolder,node,name_final,properties,config.style));
|
||||
}else{
|
||||
//write item
|
||||
write_queue.push((folder.clone(),node,name_override.clone(),properties,config.style));
|
||||
}
|
||||
//queue item to be deleted from dom after child objects are handled (stack is popped from the back)
|
||||
match node.class{
|
||||
Class::Folder=>(),
|
||||
_=>stack.push(WriteStackInstruction::Destroy(node.referent)),
|
||||
}
|
||||
if has_children{
|
||||
stack.push(WriteStackInstruction::PopFolder);
|
||||
name_tally.clear();
|
||||
for referent in &node.children{
|
||||
if let Some(c)=context.tree_refs.get(referent){
|
||||
let v=name_tally.entry(c.name.clone()).and_modify(|v|*v+=1).or_default();
|
||||
stack.push(WriteStackInstruction::Node(c,*v));
|
||||
//trim empty folders
|
||||
let mut script_count=0;
|
||||
let mut stack:Vec<TrimStackInstruction>=tree_refs.get(&dom.root_ref()).unwrap().children
|
||||
.iter().map(|&c|TrimStackInstruction::Referent(c)).collect();
|
||||
while let Some(instruction)=stack.pop(){
|
||||
match instruction{
|
||||
TrimStackInstruction::IncrementScript=>script_count+=1,
|
||||
TrimStackInstruction::DecrementScript=>script_count-=1,
|
||||
TrimStackInstruction::Referent(referent)=>{
|
||||
let mut delete=None;
|
||||
if let Some(node)=tree_refs.get_mut(&referent){
|
||||
if node.class==Class::Folder&&script_count!=0{
|
||||
node.class=Class::Model
|
||||
}
|
||||
if node.class==Class::Folder&&node.children.len()==0{
|
||||
delete=Some(node.parent);
|
||||
}else{
|
||||
//how the hell do I do this better without recursion
|
||||
let is_script=match node.class{
|
||||
Class::ModuleScript|Class::LocalScript|Class::Script=>true,
|
||||
_=>false,
|
||||
};
|
||||
//stack is popped from back
|
||||
if is_script{
|
||||
stack.push(TrimStackInstruction::DecrementScript);
|
||||
}
|
||||
for &child_referent in &node.children{
|
||||
stack.push(TrimStackInstruction::Referent(child_referent));
|
||||
}
|
||||
if is_script{
|
||||
stack.push(TrimStackInstruction::IncrementScript);
|
||||
}
|
||||
}
|
||||
}
|
||||
stack.push(WriteStackInstruction::PushFolder(sanitize(name_override.as_str()).to_string()));
|
||||
}
|
||||
},
|
||||
//trim referent
|
||||
if let Some(parent_ref)=delete{
|
||||
let parent_node=tree_refs.get_mut(&parent_ref)
|
||||
.expect("parent_ref does not exist in tree_refs");
|
||||
parent_node.children.remove(
|
||||
parent_node.children.iter()
|
||||
.position(|&r|r==referent)
|
||||
.expect("parent.children does not contain referent")
|
||||
);
|
||||
tree_refs.remove(&referent);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Self{
|
||||
dom,
|
||||
tree_refs,
|
||||
}
|
||||
}
|
||||
pub async fn write_files(mut self,config:WriteConfig)->Result<(),WriteError>{
|
||||
let mut write_queue=Vec::new();
|
||||
let mut destroy_queue=Vec::new();
|
||||
|
||||
//run the async
|
||||
{
|
||||
let dom=&context.dom;
|
||||
let write_models=config.write_models;
|
||||
let write_scripts=config.write_scripts;
|
||||
let results:Vec<AResult<()>>=rayon::iter::ParallelIterator::collect(rayon::iter::ParallelIterator::map(rayon::iter::IntoParallelIterator::into_par_iter(write_queue),|(write_path,node,node_name_override,properties,style)|{
|
||||
write_item(&dom,write_path,node,node_name_override,properties,style,write_models,write_scripts)
|
||||
}));
|
||||
for result in results{
|
||||
result?;
|
||||
let mut name_tally=std::collections::HashMap::<String,u32>::new();
|
||||
let mut folder=config.output_folder.clone();
|
||||
let mut stack=vec![WriteStackInstruction::Node(self.tree_refs.get(&self.dom.root_ref()).unwrap(),0)];
|
||||
while let Some(instruction)=stack.pop(){
|
||||
match instruction{
|
||||
WriteStackInstruction::PushFolder(component)=>folder.push(component),
|
||||
WriteStackInstruction::PopFolder=>assert!(folder.pop(),"weirdness"),
|
||||
WriteStackInstruction::Destroy(referent)=>destroy_queue.push(referent),
|
||||
WriteStackInstruction::Node(node,name_count)=>{
|
||||
//track properties that must be overriden to compile folder structure back into a place file
|
||||
let mut properties=PropertiesOverride::default();
|
||||
let has_children=node.children.len()!=0;
|
||||
match node.class{
|
||||
Class::Folder=>(),
|
||||
Class::ModuleScript=>(),//.lua files are ModuleScript by default
|
||||
Class::LocalScript=>properties.class=Some("LocalScript".to_owned()),
|
||||
Class::Script=>properties.class=Some("Script".to_owned()),
|
||||
Class::Model=>(),
|
||||
}
|
||||
let name_override=if 0<name_count{
|
||||
properties.name=Some(node.name.clone());
|
||||
format!("{}_{}",node.name,name_count)
|
||||
}else{
|
||||
node.name.clone()
|
||||
};
|
||||
|
||||
if has_children{
|
||||
//push temp subfolder
|
||||
let mut subfolder=folder.clone();
|
||||
subfolder.push(sanitize(name_override.as_str()).as_ref());
|
||||
//make folder
|
||||
tokio::fs::create_dir(subfolder.clone()).await.map_err(WriteError::IO)?;
|
||||
|
||||
let name_final=match config.style{
|
||||
Style::Rox
|
||||
|Style::RoxRojo=>name_override.clone(),
|
||||
Style::Rojo=>"init".to_owned(),
|
||||
};
|
||||
|
||||
//write item in subfolder
|
||||
write_queue.push((subfolder,node,name_final,properties,config.style));
|
||||
}else{
|
||||
//write item
|
||||
write_queue.push((folder.clone(),node,name_override.clone(),properties,config.style));
|
||||
}
|
||||
//queue item to be deleted from dom after child objects are handled (stack is popped from the back)
|
||||
match node.class{
|
||||
Class::Folder=>(),
|
||||
_=>stack.push(WriteStackInstruction::Destroy(node.referent)),
|
||||
}
|
||||
if has_children{
|
||||
stack.push(WriteStackInstruction::PopFolder);
|
||||
name_tally.clear();
|
||||
for referent in &node.children{
|
||||
if let Some(c)=self.tree_refs.get(referent){
|
||||
let v=name_tally.entry(c.name.clone()).and_modify(|v|*v+=1).or_default();
|
||||
stack.push(WriteStackInstruction::Node(c,*v));
|
||||
}
|
||||
}
|
||||
stack.push(WriteStackInstruction::PushFolder(sanitize(name_override.as_str()).to_string()));
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//run the destroy
|
||||
for destroy_ref in destroy_queue{
|
||||
context.dom.destroy(destroy_ref);
|
||||
}
|
||||
//run the async
|
||||
{
|
||||
let dom=&self.dom;
|
||||
let write_models=config.write_models;
|
||||
let write_scripts=config.write_scripts;
|
||||
let results:Vec<Result<(),WriteError>>=rayon::iter::ParallelIterator::collect(rayon::iter::ParallelIterator::map(rayon::iter::IntoParallelIterator::into_par_iter(write_queue),|(write_path,node,node_name_override,properties,style)|{
|
||||
write_item(&dom,write_path,node,node_name_override,properties,style,write_models,write_scripts)
|
||||
}));
|
||||
for result in results{
|
||||
result?;
|
||||
}
|
||||
}
|
||||
|
||||
//write what remains in template.rbxlx
|
||||
if config.write_template{
|
||||
let mut file=config.output_folder.clone();
|
||||
file.push("template");
|
||||
assert!(file.set_extension("rbxlx"));
|
||||
let output=std::io::BufWriter::new(std::fs::File::create(file)?);
|
||||
rbx_xml::to_writer_default(output,&context.dom,context.dom.root().children())?;
|
||||
}
|
||||
//run the destroy
|
||||
for destroy_ref in destroy_queue{
|
||||
self.dom.destroy(destroy_ref);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
//write what remains in template.rbxlx
|
||||
if config.write_template{
|
||||
let mut file=config.output_folder.clone();
|
||||
file.push("template");
|
||||
assert!(file.set_extension("rbxlx"));
|
||||
let output=std::io::BufWriter::new(std::fs::File::create(file).map_err(WriteError::IO)?);
|
||||
rbx_xml::to_writer_default(output,&self.dom,self.dom.root().children()).map_err(WriteError::EncodeError)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
#[derive(Clone,Copy,Debug)]
|
||||
pub enum DecompileStyle{
|
||||
pub enum Style{
|
||||
Rox,
|
||||
Rojo,
|
||||
RoxRojo,
|
||||
|
36
src/main.rs
36
src/main.rs
@ -1,7 +1,6 @@
|
||||
use std::{io::Read,path::PathBuf};
|
||||
use clap::{Args,Parser,Subcommand};
|
||||
use anyhow::Result as AResult;
|
||||
use futures::StreamExt;
|
||||
use rbx_dom_weak::types::Ref;
|
||||
use tokio::io::AsyncReadExt;
|
||||
use rbx_asset::context::{RobloxContext,InventoryItem,AssetVersion};
|
||||
@ -110,7 +109,7 @@ struct CompileSubcommand{
|
||||
#[arg(long)]
|
||||
output_file:PathBuf,
|
||||
#[arg(long)]
|
||||
style:Option<DecompileStyle>,
|
||||
style:Option<Style>,
|
||||
#[arg(long)]
|
||||
template:Option<PathBuf>,
|
||||
}
|
||||
@ -121,7 +120,7 @@ struct DecompileSubcommand{
|
||||
#[arg(long)]
|
||||
output_folder:Option<PathBuf>,
|
||||
#[arg(long)]
|
||||
style:DecompileStyle,
|
||||
style:Style,
|
||||
#[arg(long)]
|
||||
write_template:Option<bool>,
|
||||
#[arg(long)]
|
||||
@ -136,7 +135,7 @@ struct DecompileHistoryIntoGitSubcommand{
|
||||
//currently output folder must be the current folder due to git2 limitations
|
||||
//output_folder:cli.output.unwrap(),
|
||||
#[arg(long)]
|
||||
style:DecompileStyle,
|
||||
style:Style,
|
||||
#[arg(long)]
|
||||
git_committer_name:String,
|
||||
#[arg(long)]
|
||||
@ -159,7 +158,7 @@ struct DownloadAndDecompileHistoryIntoGitSubcommand{
|
||||
//currently output folder must be the current folder due to git2 limitations
|
||||
//output_folder:cli.output.unwrap(),
|
||||
#[arg(long)]
|
||||
style:DecompileStyle,
|
||||
style:Style,
|
||||
#[arg(long)]
|
||||
git_committer_name:String,
|
||||
#[arg(long)]
|
||||
@ -180,17 +179,17 @@ enum CookieType{
|
||||
}
|
||||
|
||||
#[derive(Clone,Copy,Debug,clap::ValueEnum)]
|
||||
pub enum DecompileStyle{
|
||||
pub enum Style{
|
||||
Rox,
|
||||
Rojo,
|
||||
RoxRojo,
|
||||
}
|
||||
impl DecompileStyle{
|
||||
fn rox(&self)->rox_compiler::types::DecompileStyle{
|
||||
impl Style{
|
||||
fn rox(&self)->rox_compiler::types::Style{
|
||||
match self{
|
||||
DecompileStyle::Rox=>rox_compiler::types::DecompileStyle::Rox,
|
||||
DecompileStyle::Rojo=>rox_compiler::types::DecompileStyle::Rojo,
|
||||
DecompileStyle::RoxRojo=>rox_compiler::types::DecompileStyle::RoxRojo,
|
||||
Style::Rox=>rox_compiler::types::Style::Rox,
|
||||
Style::Rojo=>rox_compiler::types::Style::Rojo,
|
||||
Style::RoxRojo=>rox_compiler::types::Style::RoxRojo,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -548,7 +547,7 @@ fn load_dom<R:Read>(input:R)->AResult<rbx_dom_weak::WeakDom>{
|
||||
|
||||
|
||||
struct DecompileConfig{
|
||||
style:DecompileStyle,
|
||||
style:Style,
|
||||
input_file:PathBuf,
|
||||
output_folder:PathBuf,
|
||||
write_template:bool,
|
||||
@ -583,7 +582,7 @@ struct WriteCommitConfig{
|
||||
git_committer_name:String,
|
||||
git_committer_email:String,
|
||||
output_folder:PathBuf,
|
||||
style:DecompileStyle,
|
||||
style:Style,
|
||||
write_template:bool,
|
||||
write_models:bool,
|
||||
write_scripts:bool,
|
||||
@ -674,7 +673,7 @@ struct DecompileHistoryConfig{
|
||||
git_committer_name:String,
|
||||
git_committer_email:String,
|
||||
input_folder:PathBuf,
|
||||
style:DecompileStyle,
|
||||
style:Style,
|
||||
output_folder:PathBuf,
|
||||
write_template:bool,
|
||||
write_models:bool,
|
||||
@ -723,7 +722,7 @@ struct DownloadAndDecompileHistoryConfig{
|
||||
asset_id:AssetID,
|
||||
git_committer_name:String,
|
||||
git_committer_email:String,
|
||||
style:DecompileStyle,
|
||||
style:Style,
|
||||
output_folder:PathBuf,
|
||||
write_template:bool,
|
||||
write_models:bool,
|
||||
@ -770,7 +769,7 @@ struct CompileConfig{
|
||||
input_folder:PathBuf,
|
||||
output_file:PathBuf,
|
||||
template:Option<PathBuf>,
|
||||
style:Option<DecompileStyle>,
|
||||
style:Option<Style>,
|
||||
}
|
||||
|
||||
async fn compile(config:CompileConfig)->AResult<()>{
|
||||
@ -784,7 +783,10 @@ async fn compile(config:CompileConfig)->AResult<()>{
|
||||
//hack to traverse root folder as the root object
|
||||
dom.root_mut().name="src".to_owned();
|
||||
|
||||
something_something_dom_write(&mut dom).await?;
|
||||
rox_compiler::compile::compile(rox_compiler::types::CompileConfig{
|
||||
input_folder:config.input_folder,
|
||||
style:config.style,
|
||||
},&mut dom).await?;
|
||||
|
||||
let mut output_place=config.output_file.clone();
|
||||
if output_place.extension().is_none()&&tokio::fs::try_exists(output_place.as_path()).await?{
|
||||
|
Loading…
Reference in New Issue
Block a user