hashcracker 1.1.3

GPU-accelerated password cracker — 42 hash types, Vulkan/Metal/DX12, single binary
use std::path::PathBuf;
use std::sync::LazyLock;

use serde::{Deserialize, Serialize};

static DEFAULT_SESSION_DIR: LazyLock<PathBuf> = LazyLock::new(|| {
    let home = std::env::var("HOME")
        .or_else(|_| std::env::var("USERPROFILE"))
        .unwrap_or_else(|_| ".".to_string());
    PathBuf::from(home).join(".hashcracker").join("sessions")
});

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionState {
    pub hash_type: String,
    pub attack_mode: String,
    pub target_hash: String,
    pub salt: String,
    pub password_len: u32,
    pub keyspace: u64,
    pub progress: u64,
    pub mask: Option<String>,
    pub wordlist: Option<String>,
    pub rules_file: Option<String>,
    pub timestamp: String,
    pub cracked_hashes: Vec<(String, String)>,
}

#[derive(Debug)]
pub struct Session {
    name: String,
    dir: PathBuf,
    state: Option<SessionState>,
}

impl Session {
    pub fn new(name: &str) -> Self {
        Session {
            name: name.to_string(),
            dir: DEFAULT_SESSION_DIR.clone(),
            state: None,
        }
    }

    #[allow(dead_code)]
    pub fn with_dir(name: &str, dir: PathBuf) -> Self {
        Session {
            name: name.to_string(),
            dir,
            state: None,
        }
    }

    fn path(&self) -> PathBuf {
        self.dir.join(format!("{}.json", self.name))
    }

    pub fn save(&self, state: &SessionState) -> Result<(), String> {
        std::fs::create_dir_all(&self.dir)
            .map_err(|e| format!("Failed to create session dir: {}", e))?;
        let json = serde_json::to_string_pretty(state)
            .map_err(|e| format!("Failed to serialize session: {}", e))?;
        std::fs::write(self.path(), json)
            .map_err(|e| format!("Failed to write session: {}", e))
    }

    pub fn load(&mut self) -> Result<SessionState, String> {
        let content = std::fs::read_to_string(self.path())
            .map_err(|e| format!("Failed to read session: {}", e))?;
        let state: SessionState = serde_json::from_str(&content)
            .map_err(|e| format!("Failed to parse session: {}", e))?;
        self.state = Some(state.clone());
        Ok(state)
    }

    pub fn exists(&self) -> bool {
        self.path().exists()
    }

    pub fn delete(&self) -> Result<(), String> {
        if self.path().exists() {
            std::fs::remove_file(self.path())
                .map_err(|e| format!("Failed to delete session: {}", e))
        } else {
            Ok(())
        }
    }

    #[allow(dead_code)]
    pub fn state(&self) -> Option<&SessionState> {
        self.state.as_ref()
    }
}