Why are strings displaying like on the side bar. It's displaying EVERY string. Find a better way to do this
Fix strings conjoining each other like this
Value: YYou currently have no enabled platforms for the selected branch. Do you wish to continue?
^ issue is "Y"

Value: 4Invalid Perforce workspace; failed with message. {0}

^ issue is "4"

Also fix symbols not showing anything. Migrate symbol finding to symbol.rs and make a highly advanced symbol searching system.

use eframe::{
    egui::{
        self, menu, CentralPanel, Color32, Context, FontId, Layout, RichText, ScrollArea, SidePanel,
        TopBottomPanel,
    },
    emath::Align,
    App, CreationContext, Frame, NativeOptions,
};
use object::{
    Object, ObjectSection, ObjectSymbol, SectionKind, SymbolKind, SymbolScope,
};
use rfd::FileDialog;
use std::{
    borrow::Cow,
    collections::HashMap,
    fs::File,
    io::Read,
};
use std::str;
use rustc_demangle::demangle;
use capstone::{prelude::*, Capstone};

fn main() {
    let app = BinaryAnalyzerApp::default();
    let native_options = NativeOptions {
        viewport: egui::ViewportBuilder::default().with_inner_size([1024.0, 768.0]),
        ..Default::default()
    };
    eframe::run_native(
        "Rust Binary Analyzer",
        native_options,
        Box::new(|cc| Ok(Box::new(app.setup(cc)))),
    )
    .expect("Failed to start eframe");
}

struct BinaryAnalyzerApp {
    binary_path: String,
    analysis_result: AnalysisResult,
    error_message: Option<String>,
    selected_section: Option<SectionInfo>,
    selected_symbol: Option<SymbolInfo>,
    dark_mode: bool,
    recent_files: Vec<String>,
    disassembly_cache: HashMap<u64, String>,
    search_query: String,
    tab_index: usize,
    selected_string: Option<StringInfo>,
}

impl Default for BinaryAnalyzerApp {
    fn default() -> Self {
        Self {
            binary_path: String::new(),
            analysis_result: AnalysisResult::default(),
            error_message: None,
            selected_section: None,
            selected_symbol: None,
            dark_mode: true,
            recent_files: Vec::new(),
            disassembly_cache: HashMap::new(),
            search_query: String::new(),
            tab_index: 0,
            selected_string: None,
        }
    }
}

impl BinaryAnalyzerApp {
    fn setup(self, cc: &CreationContext<'_>) -> Self {
        // Set initial dark mode
        if self.dark_mode {
            cc.egui_ctx.set_visuals(egui::Visuals::dark());
        } else {
            cc.egui_ctx.set_visuals(egui::Visuals::light());
        }
        self
    }
}

impl App for BinaryAnalyzerApp {
    fn update(&mut self, ctx: &Context, _: &mut Frame) {
        self.top_panel(ctx);
        self.side_panel(ctx);
        self.central_panel(ctx);
        self.status_bar(ctx);
        self.handle_file_drop(ctx);
    }
}

impl BinaryAnalyzerApp {
    /// Draws the top menu bar
    fn top_panel(&mut self, ctx: &Context) {
        TopBottomPanel::top("menu_bar").show(ctx, |ui| {
            menu::bar(ui, |ui| {
                // File Menu
                ui.menu_button("File", |ui| {
                    if ui.button("Open...").clicked() {
                        self.open_file_dialog();
                        ui.close_menu();
                    }
                    if ui.button("Exit").clicked() {
                        std::process::exit(0);
                    }
                });

                // View Menu
                ui.menu_button("View", |ui| {
                    if ui
                        .checkbox(&mut self.dark_mode, "Dark Mode")
                        .clicked()
                    {
                        if self.dark_mode {
                            ctx.set_visuals(egui::Visuals::dark());
                        } else {
                            ctx.set_visuals(egui::Visuals::light());
                        }
                    }
                    ui.separator();
                    if ui.button("Refresh").clicked() {
                        self.perform_analysis();
                        ui.close_menu();
                    }
                });

                // Help Menu
                ui.menu_button("Help", |ui| {
                    if ui.button("About").clicked() {
                        ui.close_menu();
                        self.show_about(ctx);
                    }
                });

                ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
                    ui.label(RichText::new("🔍 Rust Binary Analyzer").font(FontId::proportional(24.0)));
                });
            });
        });
    }

    /// Draws the side panel for navigation
    fn side_panel(&mut self, ctx: &Context) {
        SidePanel::left("side_panel")
            .min_width(250.0)
            .resizable(true)
            .show(ctx, |ui| {
                ui.heading("Explorer");
                ui.separator();

                if !self.analysis_result.is_empty() {
                    ui.vertical(|ui| {
                        ui.spacing_mut().item_spacing.y = 10.0;
                        ui.collapsing("📁 Sections", |ui| {
                            ScrollArea::vertical().show(ui, |ui| {
                                for section in &self.analysis_result.sections {
                                    if ui
                                        .selectable_label(
                                            self.selected_section
                                                .as_ref()
                                                .map_or(false, |s| s.name == section.name),
                                            &section.name,
                                        )
                                        .clicked()
                                    {
                                        self.selected_section = Some(section.clone());
                                        self.selected_symbol = None;
                                        self.selected_string = None;
                                        self.tab_index = 0;
                                    }
                                }
                            });
                        });

                        ui.collapsing("🔖 Symbols", |ui| {
                            ScrollArea::vertical().show(ui, |ui| {
                                for symbol in &self.analysis_result.symbols {
                                    let display_name = demangle(&symbol.name).to_string();
                                    if ui
                                        .selectable_label(
                                            self.selected_symbol
                                                .as_ref()
                                                .map_or(false, |s| s.name == symbol.name),
                                            display_name,
                                        )
                                        .clicked()
                                    {
                                        self.selected_symbol = Some(symbol.clone());
                                        self.selected_section = None;
                                        self.selected_string = None;
                                        self.tab_index = 1;
                                    }
                                }
                            });
                        });

                        ui.collapsing("💬 Strings", |ui| {
                            ScrollArea::vertical().show(ui, |ui| {
                                for string_info in &self.analysis_result.strings {
                                    if ui
                                        .selectable_label(
                                            self.selected_string
                                                .as_ref()
                                                .map_or(false, |s| s.address == string_info.address),
                                            &string_info.value,
                                        )
                                        .clicked()
                                    {
                                        self.selected_string = Some(string_info.clone());
                                        self.selected_section = None;
                                        self.selected_symbol = None;
                                        self.tab_index = 2;
                                    }
                                }
                            });
                        });
                    });
                } else {
                    ui.label("No file loaded.");
                }
            });
    }

    /// Draws the central panel displaying analysis results
    fn central_panel(&mut self, ctx: &Context) {
        CentralPanel::default().show(ctx, |ui| {
            if self.analysis_result.is_empty() {
                ui.centered_and_justified(|ui| {
                    ui.label(RichText::new("Drag and drop a binary file here or use File > Open to start.").italics());
                });
            } else {
                egui::TopBottomPanel::top("info_panel").show_inside(ui, |ui| {
                    ui.horizontal_wrapped(|ui| {
                        ui.label("Format:");
                        ui.monospace(format!("{:?}", self.analysis_result.format));
                        ui.separator();
                        ui.label("Arch:");
                        ui.monospace(format!("{:?}", self.analysis_result.architecture));
                        ui.separator();
                        ui.label("Endianness:");
                        ui.monospace(format!("{:?}", self.analysis_result.endianness));
                        ui.separator();
                        ui.label("File Size:");
                        ui.monospace(format!("{:.2} KB", self.analysis_result.file_size as f64 / 1024.0));
                    });
                });

                ui.add_space(5.0);

                let tabs = vec!["Section Details", "Symbol Details", "String Details", "Disassembly"];
                ui.horizontal(|ui| {
                    for (i, tab) in tabs.iter().enumerate() {
                        let selected = self.tab_index == i;
                        if ui
                            .selectable_label(selected, *tab)
                            .clicked()
                        {
                            self.tab_index = i;
                        }
                    }
                });
                ui.separator();
                ui.add_space(5.0);

                match self.tab_index {
                    0 => self.display_section_details(ui),
                    1 => self.display_symbol_details(ui),
                    2 => self.display_string_details(ui),
                    3 => self.display_disassembly(ui),
                    _ => {}
                }
            }
        });
    }

    /// Displays the status bar at the bottom
    fn status_bar(&self, ctx: &Context) {
        TopBottomPanel::bottom("status_bar").show(ctx, |ui| {
            ui.horizontal(|ui| {
                if !self.binary_path.is_empty() {
                    ui.label(format!("📄 File: {}", self.binary_path));
                }
                ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
                    ui.label("Ready");
                });
            });
        });
    }

    /// Displays section details in the central panel
    fn display_section_details(&mut self, ui: &mut egui::Ui) {
        if let Some(section) = &self.selected_section {
            ui.heading(format!("📄 Section: {}", section.name));
            ui.separator();
            ui.monospace(format!("Address: 0x{:X}", section.address));
            ui.monospace(format!("Size: {}", section.size));
            ui.monospace(format!("Flags: {:?}", section.flags));
            ui.add_space(10.0);

            if !section.data.is_empty() {
                ui.collapsing("🔎 Hex View", |ui| {
                    ScrollArea::both().show(ui, |ui| {
                        self.display_hex_view(ui, &section.data);
                    });
                });
            }
        } else {
            ui.label("Select a section from the left panel to view details.");
        }
    }

    /// Displays symbol details in the central panel
    fn display_symbol_details(&mut self, ui: &mut egui::Ui) {
        if let Some(symbol) = &self.selected_symbol {
            ui.heading(format!("🔖 Symbol: {}", demangle(&symbol.name)));
            ui.separator();
            ui.monospace(format!("Address: 0x{:X}", symbol.address));
            ui.monospace(format!("Size: {}", symbol.size));
            ui.monospace(format!("Kind: {:?}", symbol.kind));
            ui.monospace(format!("Scope: {:?}", symbol.scope));
            ui.add_space(10.0);
        } else {
            ui.label("Select a symbol from the left panel to view details.");
        }
    }

    /// Displays string details in the central panel
    fn display_string_details(&mut self, ui: &mut egui::Ui) {
        if let Some(string_info) = &self.selected_string {
            ui.heading("💬 String Details");
            ui.separator();
            ui.monospace(format!("Address: 0x{:X}", string_info.address));
            ui.monospace(format!("Value: {}", string_info.value));
            ui.add_space(10.0);
        } else {
            ui.label("Select a string from the left panel to view details.");
        }
    }

    /// Displays disassembly of code sections
    fn display_disassembly(&mut self, ui: &mut egui::Ui) {
        if let Some(section) = &self.selected_section {
            if section.is_executable {
                ui.heading(format!("🧩 Disassembly of {}", section.name));
                ui.separator();
                if let Some(disassembly) = self.disassembly_cache.get(&section.address) {
                    ScrollArea::vertical().show(ui, |ui| {
                        self.display_syntax_highlighted_disassembly(ui, disassembly);
                    });
                } else {
                    ui.label("Disassembling...");
                    if let Ok(disassembly) = self.perform_disassembly(section) {
                        self.disassembly_cache.insert(section.address, disassembly.clone());
                        ScrollArea::vertical().show(ui, |ui| {
                            self.display_syntax_highlighted_disassembly(ui, &disassembly);
                        });
                    } else {
                        ui.label("Failed to disassemble section.");
                    }
                }
            } else {
                ui.label("Selected section is not executable.");
            }
        } else {
            ui.label("Select an executable section to disassemble.");
        }
    }

    /// Performs the binary analysis
    fn perform_analysis(&mut self) {
        match analyze_binary(&self.binary_path) {
            Ok(result) => {
                self.analysis_result = result;
                self.error_message = None;
                self.selected_section = None;
                self.selected_symbol = None;
                self.selected_string = None;
                self.disassembly_cache.clear();
            }
            Err(e) => {
                self.analysis_result = AnalysisResult::default();
                self.error_message = Some(e);
                self.selected_section = None;
                self.selected_symbol = None;
                self.selected_string = None;
                self.disassembly_cache.clear();
            }
        }
    }

    /// Opens the file dialog to select a binary file
    fn open_file_dialog(&mut self) {
        if let Some(path) = FileDialog::new().add_filter("Binary files", &[""]).pick_file() {
            self.binary_path = path.display().to_string();
            self.perform_analysis();
            self.recent_files.push(self.binary_path.clone());
        }
    }

    /// Displays the hex view of the data
    fn display_hex_view(&self, ui: &mut egui::Ui, data: &[u8]) {
        let bytes_per_row = 16;
        let mut offset = 0;
        while offset < data.len() {
            let end = usize::min(offset + bytes_per_row, data.len());
            let row = &data[offset..end];

            let hex_row: String = row.iter().map(|b| format!("{:02X} ", b)).collect();
            let ascii_row: String = row
                .iter()
                .map(|b| {
                    if b.is_ascii_graphic() {
                        *b as char
                    } else {
                        '.'
                    }
                })
                .collect();

            ui.horizontal_wrapped(|ui| {
                ui.label(format!("{:08X}: ", offset));
                ui.monospace(&hex_row);
                ui.add_space(10.0);
                ui.monospace(&ascii_row);
            });

            offset += bytes_per_row;
        }
    }

    /// Performs disassembly on the given section
    fn perform_disassembly(&self, section: &SectionInfo) -> Result<String, String> {
        let cs = Capstone::new()
            .x86()
            .mode(arch::x86::ArchMode::Mode32)
            .syntax(arch::x86::ArchSyntax::Intel)
            .detail(false)
            .build()
            .map_err(|e| format!("Capstone error: {}", e))?;

        let insns = cs.disasm_all(&section.data, section.address)
            .map_err(|e| format!("Disassembly error: {}", e))?;

        let mut disassembly = String::new();
        for i in insns.iter() {
            disassembly.push_str(&format!("0x{:08X}\t{}\t{}\n", i.address(), i.mnemonic().unwrap_or(""), i.op_str().unwrap_or("")));
        }
        Ok(disassembly)
    }

    /// Display disassembly with basic syntax highlighting
    fn display_syntax_highlighted_disassembly(&self, ui: &mut egui::Ui, disassembly: &str) {
        for line in disassembly.lines() {
            let parts: Vec<&str> = line.splitn(3, '\t').collect();
            if parts.len() == 3 {
                ui.horizontal(|ui| {
                    ui.monospace(parts[0]);
                    ui.add_space(10.0);
                    ui.colored_label(Color32::LIGHT_BLUE, parts[1]);
                    ui.add_space(10.0);
                    ui.monospace(parts[2]);
                });
            } else {
                ui.monospace(line);
            }
        }
    }

    /// Shows the About dialog
    fn show_about(&self, ctx: &Context) {
        egui::Window::new("About Rust Binary Analyzer")
            .resizable(false)
            .show(ctx, |ui| {
                ui.label("Rust Binary Analyzer");
                ui.label("Version 1.0.0");
                ui.label("Developed with 💖 in Rust and egui");
            });
    }

    /// Handles file drop events
    fn handle_file_drop(&mut self, ctx: &egui::Context) {
        let dropped_file = ctx.input(|i| i.raw.dropped_files.first().cloned());
        if let Some(file) = dropped_file {
            if let Some(path) = file.path {
                self.binary_path = path.display().to_string();
                self.perform_analysis();
            }
        }
    }
}

struct AnalysisResult {
    format: object::BinaryFormat,
    architecture: object::Architecture,
    endianness: object::Endianness,
    file_size: u64,
    sections: Vec<SectionInfo>,
    symbols: Vec<SymbolInfo>,
    strings: Vec<StringInfo>,
}

impl Default for AnalysisResult {
    fn default() -> Self {
        AnalysisResult {
            format: object::BinaryFormat::Pe,
            architecture: object::Architecture::Unknown,
            endianness: object::Endianness::Little,
            file_size: 0,
            sections: Vec::new(),
            symbols: Vec::new(),
            strings: Vec::new(),
        }
    }
}

impl AnalysisResult {
    fn is_empty(&self) -> bool {
        self.sections.is_empty() && self.symbols.is_empty() && self.strings.is_empty()
    }
}

#[derive(Clone)]
struct SectionInfo {
    name: String,
    address: u64,
    size: u64,
    data: Vec<u8>,
    flags: object::SectionFlags,
    is_executable: bool,
}

#[derive(Clone)]
struct SymbolInfo {
    name: String,
    address: u64,
    size: u64,
    kind: SymbolKind,
    scope: SymbolScope,
}

#[derive(Clone)]
struct StringInfo {
    address: u64,
    value: String,
}

fn analyze_binary(path: &str) -> Result<AnalysisResult, String> {
    let mut file = File::open(path).map_err(|e| format!("Failed to open file: {}", e))?;
    let metadata = file.metadata().map_err(|e| format!("Failed to get file metadata: {}", e))?;
    let file_size = metadata.len();

    let mut buffer = Vec::new();
    file.read_to_end(&mut buffer)
        .map_err(|e| format!("Failed to read file: {}", e))?;

    let obj_file = object::File::parse(&*buffer).map_err(|e| format!("Failed to parse binary: {}", e))?;

    let mut sections = Vec::new();
    for section in obj_file.sections() {
        let data = section.uncompressed_data().unwrap_or(Cow::Borrowed(&[])).to_vec();
        let is_executable = section.kind() == SectionKind::Text;
        let section_info = SectionInfo {
            name: section.name().unwrap_or("Unknown").to_string(),
            address: section.address(),
            size: section.size(),
            data,
            flags: section.flags(),
            is_executable,
        };
        sections.push(section_info);
    }

    let mut symbols = Vec::new();
    for symbol in obj_file.symbols() {
        let symbol_info = SymbolInfo {
            name: symbol.name().unwrap_or("Unknown").to_string(),
            address: symbol.address(),
            size: symbol.size(),
            kind: symbol.kind(),
            scope: symbol.scope(),
        };
        symbols.push(symbol_info);
    }

    let strings = extract_strings(&buffer);

    Ok(AnalysisResult {
        format: obj_file.format(),
        architecture: obj_file.architecture(),
        endianness: obj_file.endianness(),
        file_size,
        sections,
        symbols,
        strings,
    })
} 

/// Extract printable ASCII strings from the binary
fn extract_strings(data: &[u8]) -> Vec<StringInfo> {
    let mut strings = Vec::new();
    let mut current_str = Vec::new();
    let mut start_address = 0;
    let mut in_string = false;

    for (i, &byte) in data.iter().enumerate() {
        if byte.is_ascii_graphic() || byte.is_ascii_whitespace() {
            if !in_string {
                start_address = i as u64;
                in_string = true;
            }
            current_str.push(byte);
        } else {
            if in_string {
                if current_str.len() >= 4 {
                    if let Ok(s) = String::from_utf8(current_str.clone()) {
                        strings.push(StringInfo {
                            address: start_address,
                            value: s,
                        });
                    }
                }
                current_str.clear();
                in_string = false;
            }
        }
    }
    // Check for any remaining string at the end
    if in_string && current_str.len() >= 4 {
        if let Ok(s) = String::from_utf8(current_str.clone()) {
            strings.push(StringInfo {
                address: start_address,
                value: s,
            });
        }
    }
    strings
}