2020-08-05 21:43:57 +00:00
|
|
|
use clap::Clap;
|
2019-10-11 14:19:18 +00:00
|
|
|
use indicatif::{ProgressBar, ProgressStyle};
|
|
|
|
use path_slash::PathExt;
|
2021-08-23 23:46:17 +00:00
|
|
|
use rayon::prelude::*;
|
2021-01-19 00:46:20 +00:00
|
|
|
use ruffle_core::swf::{decompress_swf, parse_swf};
|
2021-08-23 23:24:52 +00:00
|
|
|
use swf::{FileAttributes, Tag};
|
2019-10-11 14:19:18 +00:00
|
|
|
|
|
|
|
use serde::Serialize;
|
|
|
|
use std::path::{Path, PathBuf};
|
|
|
|
|
|
|
|
use std::panic::catch_unwind;
|
|
|
|
use walkdir::{DirEntry, WalkDir};
|
|
|
|
|
2021-08-23 23:24:52 +00:00
|
|
|
#[derive(Serialize, Debug)]
|
|
|
|
enum AvmType {
|
|
|
|
Avm1,
|
|
|
|
Avm2,
|
|
|
|
}
|
|
|
|
|
2021-08-24 00:05:56 +00:00
|
|
|
#[derive(Serialize, Debug)]
|
|
|
|
enum Progress {
|
|
|
|
Nothing,
|
|
|
|
Read,
|
|
|
|
Decompressed,
|
|
|
|
Parsed,
|
|
|
|
}
|
|
|
|
|
2019-10-11 14:19:18 +00:00
|
|
|
#[derive(Serialize, Debug)]
|
|
|
|
struct FileResults {
|
|
|
|
name: String,
|
2021-08-24 00:05:56 +00:00
|
|
|
progress: Progress,
|
2019-10-11 14:19:18 +00:00
|
|
|
error: Option<String>,
|
2021-08-23 23:24:52 +00:00
|
|
|
vm_type: Option<AvmType>,
|
2019-10-11 14:19:18 +00:00
|
|
|
}
|
|
|
|
|
2020-08-05 21:43:57 +00:00
|
|
|
#[derive(Clap, Debug)]
|
|
|
|
#[clap(version, about, author)]
|
2019-10-11 14:19:18 +00:00
|
|
|
struct Opt {
|
|
|
|
/// The directory (containing SWF files) to scan
|
2020-08-05 21:43:57 +00:00
|
|
|
#[clap(name = "directory", parse(from_os_str))]
|
2019-10-11 14:19:18 +00:00
|
|
|
input_path: PathBuf,
|
|
|
|
|
|
|
|
/// The file to store results in CSV format
|
2020-08-05 21:43:57 +00:00
|
|
|
#[clap(name = "results", parse(from_os_str))]
|
2019-10-11 14:19:18 +00:00
|
|
|
output_path: PathBuf,
|
|
|
|
|
|
|
|
/// Filenames to ignore
|
2020-09-22 01:56:46 +00:00
|
|
|
#[clap(short = 'i', long = "ignore")]
|
2019-10-11 14:19:18 +00:00
|
|
|
ignore: Vec<String>,
|
|
|
|
}
|
|
|
|
|
|
|
|
fn find_files(root: &Path, ignore: &[String]) -> Vec<DirEntry> {
|
|
|
|
let progress = ProgressBar::new_spinner();
|
|
|
|
let mut results = Vec::new();
|
|
|
|
|
|
|
|
for entry in WalkDir::new(root)
|
|
|
|
.follow_links(true)
|
|
|
|
.into_iter()
|
|
|
|
.filter_map(|e| e.ok())
|
|
|
|
{
|
|
|
|
let f_name = entry.file_name().to_string_lossy();
|
|
|
|
|
|
|
|
if f_name.ends_with(".swf") && !ignore.iter().any(|x| x == &f_name) {
|
|
|
|
results.push(entry);
|
2021-05-03 08:44:15 +00:00
|
|
|
progress.set_message(format!("Searching for swf files... {}", results.len()));
|
2019-10-11 14:19:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-03 08:44:15 +00:00
|
|
|
progress.finish_with_message(format!("Found {} swf files to scan", results.len()));
|
2019-10-11 14:19:18 +00:00
|
|
|
results
|
|
|
|
}
|
|
|
|
|
|
|
|
fn scan_file(file: DirEntry, name: String) -> FileResults {
|
2021-08-24 00:05:56 +00:00
|
|
|
let mut progress = Progress::Nothing;
|
|
|
|
|
2019-10-11 14:19:18 +00:00
|
|
|
let data = match std::fs::read(file.path()) {
|
|
|
|
Ok(data) => data,
|
|
|
|
Err(e) => {
|
|
|
|
return {
|
|
|
|
FileResults {
|
2021-08-24 00:05:56 +00:00
|
|
|
progress,
|
2019-10-11 14:19:18 +00:00
|
|
|
name,
|
|
|
|
error: Some(format!("File error: {}", e.to_string())),
|
2021-08-23 23:24:52 +00:00
|
|
|
vm_type: None,
|
2019-10-11 14:19:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-08-24 00:05:56 +00:00
|
|
|
progress = Progress::Read;
|
|
|
|
|
2021-08-23 23:44:51 +00:00
|
|
|
let swf_buf = match decompress_swf(&data[..]) {
|
|
|
|
Ok(swf_buf) => swf_buf,
|
|
|
|
Err(e) => {
|
|
|
|
return FileResults {
|
2021-08-24 00:05:56 +00:00
|
|
|
progress,
|
2021-08-23 23:44:51 +00:00
|
|
|
name,
|
|
|
|
error: Some(e.to_string()),
|
|
|
|
vm_type: None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2021-08-24 00:05:56 +00:00
|
|
|
|
|
|
|
progress = Progress::Decompressed;
|
|
|
|
|
|
|
|
let vm_type = match catch_unwind(|| parse_swf(&swf_buf)) {
|
2019-10-11 14:19:18 +00:00
|
|
|
Ok(swf) => match swf {
|
2021-08-23 23:24:52 +00:00
|
|
|
Ok(swf) => {
|
|
|
|
let mut vm_type = Some(AvmType::Avm1);
|
|
|
|
if let Some(Tag::FileAttributes(fa)) = swf.tags.first() {
|
|
|
|
if fa.contains(FileAttributes::IS_ACTION_SCRIPT_3) {
|
|
|
|
vm_type = Some(AvmType::Avm2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-24 00:05:56 +00:00
|
|
|
vm_type
|
|
|
|
}
|
|
|
|
Err(e) => {
|
|
|
|
return FileResults {
|
|
|
|
progress,
|
2021-08-23 23:24:52 +00:00
|
|
|
name,
|
2021-08-24 00:05:56 +00:00
|
|
|
error: Some(format!("Parse error: {}", e.to_string())),
|
|
|
|
vm_type: None,
|
2021-08-23 23:24:52 +00:00
|
|
|
}
|
|
|
|
}
|
2019-10-11 14:19:18 +00:00
|
|
|
},
|
|
|
|
Err(e) => match e.downcast::<String>() {
|
2021-08-24 00:05:56 +00:00
|
|
|
Ok(e) => {
|
|
|
|
return FileResults {
|
|
|
|
progress,
|
|
|
|
name,
|
|
|
|
error: Some(format!("PANIC: {}", e.to_string())),
|
|
|
|
vm_type: None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Err(_) => {
|
|
|
|
return FileResults {
|
|
|
|
progress,
|
|
|
|
name,
|
|
|
|
error: Some("PANIC".to_string()),
|
|
|
|
vm_type: None,
|
|
|
|
}
|
|
|
|
}
|
2019-10-11 14:19:18 +00:00
|
|
|
},
|
2021-08-24 00:05:56 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
progress = Progress::Parsed;
|
|
|
|
|
|
|
|
FileResults {
|
|
|
|
progress,
|
|
|
|
name,
|
|
|
|
error: None,
|
|
|
|
vm_type,
|
2019-10-11 14:19:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn main() -> Result<(), std::io::Error> {
|
|
|
|
env_logger::init();
|
|
|
|
|
2020-08-05 21:43:57 +00:00
|
|
|
let opt = Opt::parse();
|
2019-10-11 14:19:18 +00:00
|
|
|
let to_scan = find_files(&opt.input_path, &opt.ignore);
|
|
|
|
let total = to_scan.len() as u64;
|
|
|
|
let mut good = 0;
|
|
|
|
let mut bad = 0;
|
|
|
|
let progress = ProgressBar::new(total);
|
2021-08-23 23:46:17 +00:00
|
|
|
let mut writer = csv::Writer::from_path(opt.output_path.clone())?;
|
2019-10-11 14:19:18 +00:00
|
|
|
|
|
|
|
progress.set_style(
|
|
|
|
ProgressStyle::default_bar()
|
|
|
|
.template(
|
|
|
|
"[{elapsed_precise}] {bar:40.cyan/blue} [{eta_precise}] {pos:>7}/{len:7} {msg}",
|
|
|
|
)
|
|
|
|
.progress_chars("##-"),
|
|
|
|
);
|
|
|
|
|
2021-08-24 00:05:56 +00:00
|
|
|
writer.write_record(&["Filename", "Progress", "Error", "AVM Version"])?;
|
2019-10-11 14:19:18 +00:00
|
|
|
|
2021-08-23 23:46:17 +00:00
|
|
|
let mut results = Vec::new();
|
|
|
|
to_scan
|
|
|
|
.into_par_iter()
|
|
|
|
.map(|file| {
|
|
|
|
let name = file
|
|
|
|
.path()
|
|
|
|
.strip_prefix(&opt.input_path)
|
|
|
|
.unwrap_or_else(|_| file.path())
|
|
|
|
.to_slash_lossy();
|
|
|
|
let result = scan_file(file, name.clone());
|
|
|
|
|
|
|
|
progress.inc(1);
|
|
|
|
progress.set_message(name);
|
|
|
|
|
|
|
|
result
|
|
|
|
})
|
|
|
|
.collect_into_vec(&mut results);
|
|
|
|
|
|
|
|
for result in results {
|
2019-10-11 14:19:18 +00:00
|
|
|
if result.error.is_none() {
|
|
|
|
good += 1;
|
|
|
|
} else {
|
|
|
|
bad += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
writer.serialize(result)?;
|
|
|
|
}
|
|
|
|
|
2021-05-03 08:44:15 +00:00
|
|
|
progress.finish_with_message(format!(
|
2019-10-11 14:19:18 +00:00
|
|
|
"Scanned {} swf files. {} successfully parsed, {} encountered errors",
|
|
|
|
total, good, bad
|
|
|
|
));
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|