1#![feature(backtrace_frames)]
2
3use std::{
4 collections::HashMap,
5 env,
6 fmt::Write,
7 fs::{self},
8 path, process,
9 sync::Mutex,
10};
11
12use consts::{ExitCode, GBF_SUITE_INPUT_DIR_ENV_VAR};
13use dotenv::dotenv;
14use gbf_core::{
15 decompiler::{
16 ast::visitors::emit_context::EmitContextBuilder,
17 function_decompiler::FunctionDecompilerBuilder,
18 },
19 module::ModuleBuilder,
20};
21
22use rayon::prelude::*;
23use std::sync::LazyLock;
24
25pub mod consts;
26
27static STATS: LazyLock<Mutex<DecompStats>> = LazyLock::new(|| Mutex::new(DecompStats::new()));
28
29struct DecompStats {
30 total_scripts: usize,
31 total_functions: usize,
32 successful_functions: usize,
33 error_counts: HashMap<String, usize>,
34}
35
36impl DecompStats {
37 fn new() -> Self {
38 Self {
39 total_scripts: 0,
40 total_functions: 0,
41 successful_functions: 0,
42 error_counts: HashMap::new(),
43 }
44 }
45
46 fn add_script(&mut self, function_count: usize) {
47 self.total_scripts += 1;
48 self.total_functions += function_count;
49 }
50
51 fn add_success(&mut self) {
52 self.successful_functions += 1;
53 }
54
55 fn add_error(&mut self, error: String) {
56 *self.error_counts.entry(error).or_insert(0) += 1;
57 }
58
59 fn log_stats(&mut self) {
60 let coverage = if self.total_functions > 0 {
61 (self.successful_functions as f32 / self.total_functions as f32) * 100.0
62 } else {
63 0.0
64 };
65
66 let mut errors: Vec<_> = self.error_counts.iter().collect();
68 errors.sort_by(|a, b| b.1.cmp(a.1));
69 let top_errors: Vec<_> = errors.iter().take(10).collect();
70
71 log::info!(
72 "Decompilation Statistics:\n\
73 Total Scripts: {}\n\
74 Total Functions: {}\n\
75 Total Coverage: {:.1}%\n\
76 Top 10 most common errors:{}",
77 self.total_scripts,
78 self.total_functions,
79 coverage,
80 if top_errors.is_empty() {
81 "\nNone".to_string()
82 } else {
83 top_errors.iter().enumerate().fold(
84 String::new(),
85 |mut output, (i, (err, count))| {
86 let _ = write!(output, "\n{}. {} ({})", i + 1, err, count);
87 output
88 },
89 )
90 }
91 );
92 }
93}
94
95fn main() {
96 dotenv().ok();
98
99 let config_path = path::Path::new(env!("CARGO_MANIFEST_DIR")).join("logging_config.yaml");
100 log4rs::init_file(config_path, Default::default()).unwrap();
101
102 let value = env::var(GBF_SUITE_INPUT_DIR_ENV_VAR).unwrap_or_else(|_| {
104 log::error!(
105 "Environment variable {} not found",
106 GBF_SUITE_INPUT_DIR_ENV_VAR
107 );
108 process::exit(ExitCode::EnvVarNotFound.into());
109 });
110
111 let dir = std::fs::read_dir(value).unwrap_or_else(|_| {
113 log::error!("Invalid directory");
114 process::exit(ExitCode::InvalidDir.into());
115 });
116
117 dir.par_bridge().for_each(|entry| {
119 let entry = match entry {
120 Ok(entry) => entry,
121 Err(err) => {
122 log::error!("Failed to get the directory entry: {}", err);
123 return;
124 }
125 };
126
127 let result = process_module(entry.path().as_ref());
128 if let Err(e) = result {
129 log::error!("Failed to process module: {}", e);
130 std::process::exit(ExitCode::UnexpectedError.into());
131 }
132 });
133
134 STATS.lock().unwrap().log_stats();
136}
137
138fn process_module(path: &path::Path) -> Result<(), Box<dyn std::error::Error>> {
141 let module_name = path
142 .file_name()
143 .ok_or("Failed to get file name")?
144 .to_str()
145 .ok_or("Failed to convert file name to string")?
146 .to_string();
147
148 let reader = fs::File::open(path)?;
149
150 let module = match ModuleBuilder::new()
151 .name(module_name.clone())
152 .reader(Box::new(reader))
153 .build()
154 {
155 Ok(module) => module,
156 Err(e) => {
157 log::error!("Failed to build module {}: {:?}", module_name, e);
158 return Ok(()); }
160 };
161
162 STATS.lock().unwrap().add_script(module.len());
165
166 module.par_iter().for_each(|func| {
168 let func_name = func.id.name.clone().unwrap_or_else(|| "entry".to_string());
169 let func_insts_count = func.iter().map(|bb| bb.len()).sum::<usize>();
170 log::info!(
171 "Decompiling function {} in module {} ({} instructions)",
172 func_name,
173 module_name,
174 func_insts_count
175 );
176
177 let mut decompiler = FunctionDecompilerBuilder::new(func)
178 .structure_analysis_max_iterations(1000)
179 .structure_debug_mode(false)
180 .build();
181
182 let res = decompiler.decompile(
183 EmitContextBuilder::default()
184 .include_ssa_versions(true)
185 .build(),
186 );
187
188 match res {
189 Ok(_) => {
190 STATS.lock().unwrap().add_success();
191 log::info!("Decompiled function {}", func_name);
192 }
193 Err(e) => {
194 STATS.lock().unwrap().add_error(e.to_string());
195 log::error!(
196 "Error decompiling function {} in module {} ({} instructions): {}",
197 func_name,
198 module_name,
199 func_insts_count,
200 e
201 );
202 }
203 }
204 });
205
206 Ok(())
207}