1#![feature(backtrace_frames)]
2
3use clap::{Parser, ValueEnum};
4use gbf_core::{
5 cfg_dot::{CfgDotConfig, DotRenderableGraph},
6 decompiler::{
7 ast::visitors::emit_context::{
8 EmitContext, EmitContextBuilder, EmitVerbosity, IndentStyle,
9 },
10 function_decompiler::FunctionDecompilerBuilder,
11 },
12 function::Function,
13 module::{Module, ModuleBuilder},
14};
15use log::{error, info};
16use rayon::prelude::*;
17use std::{
18 fs,
19 io::Read,
20 path::{Path, PathBuf},
21};
22
23#[derive(Parser, Debug)]
25#[clap(
26 name = "gbf_driver",
27 version,
28 about = "Driver for decompiling GBF modules and functions"
29)]
30struct Cli {
31 #[clap(value_parser)]
33 input: PathBuf,
34
35 #[clap(long, value_parser)]
37 output: Option<PathBuf>,
38
39 #[clap(short, long, value_enum, default_value_t = Mode::Module)]
41 mode: Mode,
42
43 #[clap(short, long)]
46 function: Option<String>,
47
48 #[clap(long)]
50 minified: bool,
51
52 #[clap(long)]
54 no_ssa: bool,
55
56 #[clap(long, value_enum, default_value_t = Indent::Allman)]
58 indent: Indent,
59
60 #[clap(long)]
62 no_hex: bool,
63
64 #[clap(long)]
66 graphviz: bool,
67
68 #[clap(long, value_parser)]
70 graphviz_output: Option<PathBuf>,
71}
72
73#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)]
75enum Mode {
76 Module,
77 Function,
78}
79
80#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)]
82enum Indent {
83 Allman,
84 KAndR,
85}
86
87fn main() {
88 let config_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("logging_config.yaml");
90 if let Err(e) = log4rs::init_file(config_path, Default::default()) {
91 eprintln!("Failed to initialize logging: {}", e);
92 std::process::exit(1);
93 }
94
95 let args = Cli::parse();
96
97 let emit_context: EmitContext = EmitContextBuilder::default()
99 .verbosity(if args.minified {
100 EmitVerbosity::Minified
101 } else {
102 EmitVerbosity::Pretty
103 })
104 .include_ssa_versions(!args.no_ssa)
105 .indent_style(match args.indent {
106 Indent::Allman => IndentStyle::Allman,
107 Indent::KAndR => IndentStyle::KAndR,
108 })
109 .format_number_hex(!args.no_hex)
110 .build();
111
112 if args.input.is_dir() {
114 fs::read_dir(&args.input)
115 .unwrap_or_else(|e| {
116 error!("Failed to read directory {}: {}", args.input.display(), e);
117 std::process::exit(1);
118 })
119 .par_bridge()
120 .for_each(|entry| {
121 let entry = match entry {
122 Ok(ent) => ent,
123 Err(e) => {
124 error!("Failed to get directory entry: {}", e);
125 return;
126 }
127 };
128
129 let path = entry.path();
130 if path.is_file() {
131 process_file(&path, &args, &emit_context);
132 }
133 });
134 } else if args.input.is_file() {
135 process_file(&args.input, &args, &emit_context);
136 } else {
137 error!(
138 "Input path {} is not a valid file or directory",
139 args.input.display()
140 );
141 std::process::exit(1);
142 }
143}
144
145fn process_file(path: &Path, args: &Cli, context: &EmitContext) {
147 info!("Processing file: {}", path.display());
148
149 let file = match fs::File::open(path) {
150 Ok(f) => f,
151 Err(e) => {
152 error!("Failed to open file {}: {}", path.display(), e);
153 return;
154 }
155 };
156
157 let module = match ModuleBuilder::new()
158 .name(
159 path.file_name()
160 .map(|s| s.to_string_lossy().into_owned())
161 .unwrap_or_else(|| "unknown_module".to_string()),
162 )
163 .reader(Box::new(file) as Box<dyn Read>)
164 .build()
165 {
166 Ok(m) => m,
167 Err(e) => {
168 error!("Failed to build module from {}: {:?}", path.display(), e);
169 return;
170 }
171 };
172
173 match args.mode {
174 Mode::Module => {
175 match module.decompile(*context) {
177 Ok(decompiled) => {
178 info!("--- Decompiled module from {} ---", path.display());
179 if let Some(ref output_dir) = args.output {
180 let base = path
181 .file_stem()
182 .map(|s| s.to_string_lossy())
183 .unwrap_or_else(|| "module".into());
184 let output_path = output_dir.join(format!("{}.gs2", base));
185 if let Err(e) = fs::write(&output_path, &decompiled) {
186 error!(
187 "Failed to write decompiled module to {}: {}",
188 output_path.display(),
189 e
190 );
191 } else {
192 info!("Decompiled module written to {}", output_path.display());
193 }
194 } else {
195 println!("{}", decompiled);
196 }
197 if args.graphviz {
198 output_module_graphviz(&module, path, args);
199 }
200 }
201 Err(e) => {
202 error!(
203 "Module decompilation failed for {}: {:?}",
204 path.display(),
205 e
206 );
207 }
208 }
209 }
210 Mode::Function => {
211 if let Some(func_name) = &args.function {
212 let func = match module.get_function_by_name(func_name).ok() {
214 Some(f) => f,
215 None => {
216 error!(
217 "Function '{}' not found in module {}",
218 func_name,
219 path.display()
220 );
221 return;
222 }
223 };
224 decompile_and_output_function(func, &module, path, args, context);
225 } else {
226 let funcs: Vec<_> = module.iter().collect();
228 funcs.par_iter().for_each(|func| {
229 let func_name = func.id.name.clone().unwrap_or_else(|| "entry".to_string());
230 let func_insts_count = func.iter().map(|bb| bb.len()).sum::<usize>();
231 info!(
232 "Decompiling function {} in module {} ({} instructions)",
233 func_name,
234 module.name.clone().unwrap_or_else(|| "unknown".to_string()),
235 func_insts_count
236 );
237 let func_context = EmitContextBuilder::default()
239 .include_ssa_versions(true)
240 .build();
241 let mut decompiler = FunctionDecompilerBuilder::new(func)
242 .structure_analysis_max_iterations(1000)
243 .structure_debug_mode(false)
244 .build();
245 let res = decompiler.decompile(func_context);
246 match res {
247 Ok(result) => {
248 info!("Decompiled function {}", func_name);
249 if let Some(ref output_dir) = args.output {
250 let output_path = output_dir.join(format!(
251 "{}_{}.gs2",
252 module.name.clone().unwrap_or_else(|| "unknown".to_string()),
253 func_name
254 ));
255 if let Err(e) = fs::write(&output_path, &result) {
256 error!(
257 "Failed to write decompiled function to {}: {}",
258 output_path.display(),
259 e
260 );
261 } else {
262 info!(
263 "Decompiled function written to {}",
264 output_path.display()
265 );
266 }
267 } else {
268 println!("--- Decompiled function '{}' ---\n{}", func_name, result);
269 }
270 }
271 Err(e) => {
272 error!(
273 "Error decompiling function {} in module {} ({} instructions): {}",
274 func_name,
275 module.name.clone().unwrap_or_else(|| "unknown".to_string()),
276 func_insts_count,
277 e
278 );
279 }
280 }
281 if args.graphviz {
282 output_function_graphviz(func, path, args);
283 }
284 });
285 }
286 }
287 }
288}
289
290fn decompile_and_output_function(
292 func: &Function,
293 module: &Module,
294 path: &Path,
295 args: &Cli,
296 context: &EmitContext,
297) {
298 let func_name = func.id.name.clone().unwrap_or_else(|| "entry".to_string());
299 let mut decompiler = FunctionDecompilerBuilder::new(func).build();
300 match decompiler.decompile(*context) {
301 Ok(result) => {
302 info!(
303 "--- Decompiled function '{}' from {} ---",
304 func_name,
305 path.display()
306 );
307 if let Some(ref output_dir) = args.output {
308 let output_path = output_dir.join(format!(
309 "{}_{}.gs2",
310 module.name.clone().unwrap_or_else(|| "unknown".to_string()),
311 func_name
312 ));
313 if let Err(e) = fs::write(&output_path, &result) {
314 error!(
315 "Failed to write decompiled function to {}: {}",
316 output_path.display(),
317 e
318 );
319 } else {
320 info!("Decompiled function written to {}", output_path.display());
321 }
322 } else {
323 println!("{}", result);
324 }
325 }
326 Err(e) => {
327 error!(
328 "Function decompilation failed for {} in {}: {:?}",
329 func_name,
330 path.display(),
331 e
332 );
333 }
334 }
335}
336
337fn output_module_graphviz(module: &Module, input_path: &Path, args: &Cli) {
339 if let Some(ref output_dir) = args.graphviz_output {
340 for func in module.iter() {
342 let dot = func.render_dot(CfgDotConfig::default());
343 let func_name = func.id.name.clone().unwrap_or_else(|| "entry".to_string());
344 let filename = format!(
345 "{}_{}_cfg.dot",
346 module.name.clone().unwrap_or_else(|| "unknown".to_string()),
347 func_name
348 );
349 let dot_path = output_dir.join(filename);
350 if let Err(e) = fs::write(&dot_path, &dot) {
351 error!(
352 "Failed to write Graphviz CFG to {}: {}",
353 dot_path.display(),
354 e
355 );
356 } else {
357 info!("Graphviz CFG written to {}", dot_path.display());
358 }
359 }
360 } else {
361 for func in module.iter() {
363 let dot = func.render_dot(CfgDotConfig::default());
364 let func_name = func.id.name.clone().unwrap_or_else(|| "entry".to_string());
365 println!(
366 "\n--- Graphviz CFG for function '{}' in module '{}' ---\n{}\n",
367 func_name,
368 input_path.display(),
369 dot
370 );
371 }
372 }
373}
374
375fn output_function_graphviz(func: &Function, input_path: &Path, args: &Cli) {
377 if let Some(ref output_dir) = args.graphviz_output {
378 let dot = func.render_dot(CfgDotConfig::default());
379 let base = input_path
380 .file_stem()
381 .map(|s| s.to_string_lossy())
382 .unwrap_or_else(|| "module".into());
383 let func_name = func.id.name.clone().unwrap_or_else(|| "entry".to_string());
384 let filename = format!("{}_{}_cfg.dot", base, func_name);
385 let dot_path = output_dir.join(filename);
386 if let Err(e) = fs::write(&dot_path, &dot) {
387 error!(
388 "Failed to write Graphviz CFG to {}: {}",
389 dot_path.display(),
390 e
391 );
392 } else {
393 info!("Graphviz CFG written to {}", dot_path.display());
394 }
395 } else {
396 let dot = func.render_dot(CfgDotConfig::default());
397 let func_name = func.id.name.clone().unwrap_or_else(|| "entry".to_string());
398 println!(
399 "\n--- Graphviz CFG for function '{}' in module '{}' ---\n{}\n",
400 func_name,
401 input_path.display(),
402 dot
403 );
404 }
405}