/// Warlock's Stave — egui GUI Demo /// /// Auto-steps through a realistic x86-64 instruction stream, feeding each /// frame through the core analysis pipeline (branch prediction, dictionary /// scanning, heatmap accumulation) and rendering the results on the 4-quarter /// hex grid workspace. /// /// Run: cargo run --bin gui_demo /// /// Controls: /// Space — toggle auto-step /// Right — single step forward /// R — reset demo state /// Esc / Q — quit use eframe::egui::{self, Color32, RichText}; use stave_core::stave_ui::{ AxialCoordinate, VisualCellMetrics, WarlockAppWorkspace, }; use stave_core::{BranchEvaluator, DictionaryRule, MemoryScanner}; // --------------------------------------------------------------------------- // Instruction stream: realistic x86-64 bytes with hex + ASM labels // --------------------------------------------------------------------------- const STREAM: &[(&[u8], &str, &str)] = &[ (&[0x48, 0x31, 0xC0], "48 31 C0 ", "xor rax, rax"), (&[0x48, 0x89, 0xC3], "48 89 C3 ", "mov rbx, rax"), (&[0x0F, 0x84, 0x10, 0x00, 0x00, 0x00], "0F 84 10 00 00 00 ", "je +0x10"), (&[0xE9, 0x05, 0x00, 0x00, 0x00], "E9 05 00 00 00 ", "jmp +0x05"), (&[0x48, 0x83, 0xC4, 0x28], "48 83 C4 28 ", "add rsp, 0x28"), (&[0x48, 0x8D, 0x05, 0x00, 0x00, 0x00, 0x00], "48 8D 05 00 00 00 00 ", "lea rax, [rip]"), (&[0x0F, 0x85, 0x20, 0x00, 0x00, 0x00], "0F 85 20 00 00 00 ", "jne +0x20"), (&[0x48, 0x31, 0xFF], "48 31 FF ", "xor rdi, rdi"), // This one contains "http://" — dictionary scanner will flag it (&[0x68, 0x74, 0x74, 0x70, 0x3A, 0x2F, 0x2F], "68 74 74 70 3A 2F 2F ", "push 0x2F2F7074_7474_7868"), (&[0xC3], "C3 ", "ret"), // Loop back: second pass re-visits cells (heatmap intensity increases) (&[0x48, 0x31, 0xC0], "48 31 C0 ", "xor rax, rax"), (&[0x0F, 0x84, 0x10, 0x00, 0x00, 0x00], "0F 84 10 00 00 00 ", "je +0x10"), (&[0x48, 0x89, 0xE5], "48 89 E5 ", "mov rbp, rsp"), (&[0x48, 0x83, 0xEC, 0x20], "48 83 EC 20 ", "sub rsp, 0x20"), (&[0xE8, 0x00, 0x00, 0x00, 0x00], "E8 00 00 00 00 ", "call +0x00"), ]; /// Hex positions for each instruction — laid out in a visible cluster. /// When the stream loops, cells get revisited and their intensity grows. const HEX_POS: &[(i32, i32)] = &[ ( 0, 0), ( 1, 0), ( 2, 0), ( 3, 0), ( 2, -1), ( 1, -1), ( 0, -1), (-1, -1), (-1, 0), ( 0, 1), // Second pass — overlaps first pass cells ( 0, 0), ( 2, 0), ( 1, 1), ( 2, 1), ( 3, 1), ]; const COLOR_NEON: Color32 = Color32::from_rgb(0, 230, 180); const COLOR_ACCENT: Color32 = Color32::from_rgb(0, 150, 255); const COLOR_DIM: Color32 = Color32::from_rgb(120, 120, 130); const COLOR_BG: Color32 = Color32::from_rgb(10, 10, 12); // --------------------------------------------------------------------------- // Demo application state // --------------------------------------------------------------------------- struct GuiDemo { workspace: WarlockAppWorkspace, step_index: usize, auto_step: bool, interval_ms: u64, last_step: std::time::Instant, total_steps: u64, branch_log: Vec<(u64, String, String)>, dict_log: Vec<(u64, String)>, } impl GuiDemo { fn new(_cc: &eframe::CreationContext<'_>) -> Self { let mut ws = WarlockAppWorkspace::new(); // Clear seed data — we'll populate from the instruction stream ws.cell_database.clear(); ws.register_states.clear(); ws.disassembly_buffer.clear(); Self { workspace: ws, step_index: 0, auto_step: true, interval_ms: 350, last_step: std::time::Instant::now(), total_steps: 0, branch_log: Vec::new(), dict_log: Vec::new(), } } /// Feed one instruction through the analysis pipeline. fn do_step(&mut self) { let idx = self.step_index % STREAM.len(); let (bytes, hex, asm) = STREAM[idx]; let rip = 0x7FF0 + idx as u64 * 16; // Alternate EFLAGS to exercise conditional / not-taken paths let eflags: u32 = match idx % 4 { 0 => 0x202, // ZF=1 1 => 0x246, // ZF=1, SF=1 2 => 0x282, // ZF=0 (je won't be taken) _ => 0x242, // ZF=1, SF=1 }; // --- Branch evaluation --- let branch = BranchEvaluator::evaluate_execution_flow(bytes, rip, eflags); let branch_str = format!("{:?}", branch); // --- Dictionary scan --- let dict_hits = MemoryScanner::scan_buffer( bytes, &DictionaryRule::get_default_hunting_library(), ); // --- Update heatmap cell --- let pos = HEX_POS[idx]; let coord = AxialCoordinate { q: pos.0, r: pos.1 }; let entry = self .workspace .cell_database .entry(coord) .or_insert(VisualCellMetrics { total_hits: 0, is_breakpoint: asm.starts_with("je") || asm.starts_with("jne"), address_reference: rip, }); entry.total_hits += 1; // --- Disassembly buffer --- self.workspace .disassembly_buffer .push((rip, hex.to_string(), asm.to_string())); if self.workspace.disassembly_buffer.len() > 60 { self.workspace.disassembly_buffer.remove(0); } // --- Register states (simulated) --- let rip_val = rip + bytes.len() as u64; let rax_val = match asm { "xor rax, rax" => 0, _ => 0xDEAD_0000_0000_0000 + self.total_steps as u64, }; let rsp_val = 0x7FFF_FFFF_E000 - self.total_steps as u64 * 0x28; self.workspace .register_states .insert("RIP".to_string(), (rip_val, true)); self.workspace .register_states .insert("RAX".to_string(), (rax_val, asm.contains("rax"))); self.workspace .register_states .insert("RBX".to_string(), (0x0000_7FFF_E000, asm.contains("rbx"))); self.workspace .register_states .insert("RSP".to_string(), (rsp_val, asm.contains("rsp"))); self.workspace .register_states .insert("RDI".to_string(), (0, asm.contains("rdi"))); self.workspace .register_states .insert("RBP".to_string(), (rsp_val, asm.contains("rbp"))); self.workspace .register_states .insert("EFLAGS".to_string(), (eflags as u64, true)); // --- Logging --- if !branch_str.contains("NonControlFlow") { self.branch_log .push((rip, asm.to_string(), branch_str)); if self.branch_log.len() > 30 { self.branch_log.remove(0); } } for dm in &dict_hits { self.dict_log .push((rip, format!("[{}] {}", dm.label, asm))); if self.dict_log.len() > 20 { self.dict_log.remove(0); } } self.step_index += 1; self.total_steps += 1; } /// Render a compact control bar above the workspace. fn render_controls(&mut self, ctx: &egui::Context) { egui::TopBottomPanel::top("controls").show(ctx, |ui| { ui.set_height(36.0); ui.horizontal(|ui| { ui.style_mut().visuals.panel_fill = COLOR_BG; // Play / Pause let label = if self.auto_step { "Pause" } else { "Play" }; if ui.button(label).clicked() { self.auto_step = !self.auto_step; } // Single step if ui.button("Step").clicked() { self.do_step(); } // Speed slider ui.label(RichText::new("Speed:").color(COLOR_DIM)); ui.add(egui::Slider::new(&mut self.interval_ms, 50..=1000).suffix("ms")); // Reset if ui.button("Reset").clicked() { self.step_index = 0; self.total_steps = 0; self.workspace.cell_database.clear(); self.workspace.register_states.clear(); self.workspace.disassembly_buffer.clear(); self.branch_log.clear(); self.dict_log.clear(); } ui.separator(); // Status ui.label( RichText::new(format!( "Frame {} | Cells {} | Dict hits {}", self.total_steps, self.workspace.cell_database.len(), self.dict_log.len(), )) .color(COLOR_NEON), ); }); }); } /// Render the live log panel below the workspace. fn render_log_panel(&self, ctx: &egui::Context) { egui::TopBottomPanel::bottom("log").show(ctx, |ui| { ui.set_height(100.0); ui.style_mut().visuals.panel_fill = COLOR_BG; ui.vertical(|ui| { ui.horizontal(|ui| { ui.label( RichText::new("BRANCH EVALUATOR LOG") .strong() .color(COLOR_ACCENT), ); ui.separator(); ui.label( RichText::new("DICTIONARY SCANNER LOG") .strong() .color(Color32::from_rgb(255, 200, 50)), ); }); egui::ScrollArea::vertical() .max_height(72.0) .show(ui, |ui| { ui.horizontal(|ui| { // Branch log (left half) ui.vertical(|ui| { for (rip, asm, outcome) in &self.branch_log { ui.monospace( RichText::new(format!( " {:#010X} {} -> {}", rip, asm, outcome )) .color(Color32::from_rgb(180, 220, 255)), ); } }); ui.separator(); // Dict log (right half) ui.vertical(|ui| { for (rip, msg) in &self.dict_log { ui.monospace( RichText::new(format!( " {:#010X} {}", rip, msg )) .color(Color32::from_rgb(255, 220, 100)), ); } }); }); }); }); }); } } impl eframe::App for GuiDemo { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { // Auto-step timer if self.auto_step { if self.last_step.elapsed().as_millis() as u64 >= self.interval_ms { self.do_step(); self.last_step = std::time::Instant::now(); } ctx.request_repaint_after(std::time::Duration::from_millis(50)); } // Keyboard shortcuts ctx.input(|i| { if i.key_pressed(egui::Key::Space) { self.auto_step = !self.auto_step; } if i.key_pressed(egui::Key::ArrowRight) { self.do_step(); } if i.key_pressed(egui::Key::R) { self.step_index = 0; self.total_steps = 0; self.workspace.cell_database.clear(); self.workspace.register_states.clear(); self.workspace.disassembly_buffer.clear(); self.branch_log.clear(); self.dict_log.clear(); } }); // Render: controls (top) → workspace (center) → log (bottom) self.render_controls(ctx); self.workspace.update_workspace_layout(ctx); self.render_log_panel(ctx); } } fn main() -> eframe::Result<()> { let options = eframe::NativeOptions { viewport: egui::ViewportBuilder::default() .with_title("Warlock's Stave — GUI Demo") .with_inner_size([1280.0, 860.0]) .with_min_inner_size([800.0, 500.0]), ..Default::default() }; eframe::run_native( "Warlock's Stave", options, Box::new(|cc| Box::new(GuiDemo::new(cc))), ) }