Skip to main content

ghidra_cli/
config.rs

1use crate::error::{GhidraError, Result};
2use serde::{Deserialize, Serialize};
3use std::fs;
4use std::path::PathBuf;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct Config {
8    pub ghidra_install_dir: Option<PathBuf>,
9    pub ghidra_project_dir: Option<PathBuf>,
10    pub default_program: Option<String>,
11    pub default_project: Option<String>,
12    pub default_output_format: Option<String>,
13    pub default_limit: Option<usize>,
14    pub timeout: Option<u64>,
15    pub aliases: std::collections::HashMap<String, String>,
16}
17
18impl Default for Config {
19    fn default() -> Self {
20        Self {
21            ghidra_install_dir: None,
22            ghidra_project_dir: None,
23            default_program: None,
24            default_project: None,
25            default_output_format: Some("auto".to_string()),
26            default_limit: Some(1000),
27            timeout: Some(300),
28            aliases: std::collections::HashMap::new(),
29        }
30    }
31}
32
33impl Config {
34    pub fn load() -> Result<Self> {
35        let config_path = Self::config_path()?;
36
37        if !config_path.exists() {
38            return Ok(Self::default());
39        }
40
41        let content = fs::read_to_string(&config_path)?;
42        let config: Config = serde_yaml::from_str(&content)?;
43
44        Ok(config)
45    }
46
47    pub fn save(&self) -> Result<()> {
48        let config_path = Self::config_path()?;
49
50        if let Some(parent) = config_path.parent() {
51            fs::create_dir_all(parent)?;
52        }
53
54        let content = serde_yaml::to_string(self)?;
55        fs::write(config_path, content)?;
56
57        Ok(())
58    }
59
60    pub fn config_path() -> Result<PathBuf> {
61        // Check for override via environment variable
62        if let Ok(path) = std::env::var("GHIDRA_CLI_CONFIG") {
63            return Ok(PathBuf::from(path));
64        }
65
66        let config_dir = dirs::config_dir().ok_or_else(|| {
67            GhidraError::ConfigError("Could not determine config directory".to_string())
68        })?;
69
70        Ok(config_dir.join("ghidra-cli").join("config.yaml"))
71    }
72
73    pub fn get_ghidra_install_dir(&self) -> Result<PathBuf> {
74        // Check environment variable first
75        if let Ok(dir) = std::env::var("GHIDRA_INSTALL_DIR") {
76            return Ok(PathBuf::from(dir));
77        }
78
79        // Check config
80        if let Some(dir) = &self.ghidra_install_dir {
81            return Ok(dir.clone());
82        }
83
84        // Try to auto-detect on Windows
85        #[cfg(target_os = "windows")]
86        {
87            if let Some(dir) = Self::detect_ghidra_windows() {
88                return Ok(dir);
89            }
90        }
91
92        Err(GhidraError::GhidraNotFound)
93    }
94
95    pub fn get_project_dir(&self) -> Result<PathBuf> {
96        // Check environment variable first
97        if let Ok(dir) = std::env::var("GHIDRA_PROJECT_DIR") {
98            return Ok(PathBuf::from(dir));
99        }
100
101        // Check config
102        if let Some(dir) = &self.ghidra_project_dir {
103            return Ok(dir.clone());
104        }
105
106        // Default to cache dir (e.g., ~/.cache/ghidra-cli/projects)
107        let cache_dir = dirs::cache_dir().ok_or_else(|| {
108            GhidraError::ConfigError("Could not determine cache directory".to_string())
109        })?;
110
111        Ok(cache_dir.join("ghidra-cli").join("projects"))
112    }
113
114    #[cfg(target_os = "windows")]
115    pub fn detect_ghidra_windows() -> Option<PathBuf> {
116        // Helper function to check if a path is a valid Ghidra installation
117        let is_valid_ghidra =
118            |path: &PathBuf| -> bool { path.join("support").join("analyzeHeadless.bat").exists() };
119
120        // Check common installation paths
121        let mut common_paths = vec![
122            PathBuf::from("C:\\Program Files\\Ghidra"),
123            PathBuf::from("C:\\Program Files (x86)\\Ghidra"),
124            PathBuf::from("C:\\ghidra"),
125        ];
126
127        // Add user's home directory paths
128        if let Some(home) = dirs::home_dir() {
129            common_paths.push(home.join("ghidra"));
130        }
131
132        for path in common_paths {
133            if !path.exists() {
134                continue;
135            }
136
137            // First check if the path itself is a Ghidra installation
138            if is_valid_ghidra(&path) {
139                return Some(path);
140            }
141
142            // Look for ghidra_* subdirectories
143            if let Ok(entries) = fs::read_dir(&path) {
144                for entry in entries.flatten() {
145                    let entry_path = entry.path();
146                    if entry_path.is_dir() {
147                        let name = entry_path.file_name()?.to_str()?;
148                        if name.starts_with("ghidra_") && is_valid_ghidra(&entry_path) {
149                            return Some(entry_path);
150                        }
151                    }
152                }
153            }
154        }
155
156        None
157    }
158
159    pub fn get_default_program(&self) -> Option<String> {
160        std::env::var("GHIDRA_DEFAULT_PROGRAM")
161            .ok()
162            .or_else(|| self.default_program.clone())
163    }
164}
165
166#[cfg(test)]
167mod tests {
168    use super::*;
169
170    #[test]
171    fn test_default_config() {
172        let config = Config::default();
173        assert_eq!(config.timeout, Some(300));
174        assert_eq!(config.default_limit, Some(1000));
175    }
176}