#![windows_subsystem = "windows"] mod acr122u; mod mifare; mod ndef; mod ntag; use acr122u::{Acr122u, CardInfo, LedBuzzerConfig}; use eframe::egui; use mifare::{MifareDump, MifareHandler}; use ndef::NdefCodec; use ntag::NtagHandler; use pcsc::Context; use std::fs; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc::{channel, Receiver, Sender}; use std::sync::{Arc, Mutex}; use std::thread; use std::time::Duration; pub enum CardEvent { ReaderStatus { connected: bool, name: String }, CardDetected(CardInfo), CardRemoved, } pub struct NfcApp { rx: Receiver, reader_name: String, is_reader_connected: bool, current_card: Option, event_logs: Vec, active_tab: Tab, // LED / Beep state led_red: bool, led_green: bool, led_blink: bool, led_beep: bool, // MIFARE state mifare_dump: Option, mifare_status: String, // NDEF & Text State ndef_subtab: NdefSubTab, ndef_url_input: String, ndef_text_input: String, ndef_text_lang: String, wifi_ssid: String, wifi_pass: String, vcard_name: String, vcard_phone: String, vcard_email: String, vcard_company: String, geo_url: String, aar_package: String, ndef_status: String, // NTAG Password State pwd_input: String, pack_input: String, pwd_status: String, // APDU State apdu_input: String, apdu_response_log: Vec, pcsc_ctx: Arc>, is_busy: Arc, } #[derive(PartialEq)] enum Tab { Scanner, Ndef, Mifare, Password, Hardware, ExpertApdu, } #[derive(PartialEq)] enum NdefSubTab { Text, Url, Wifi, VCard, Geo, AppLauncher, } impl NfcApp { pub fn new(cc: &eframe::CreationContext<'_>) -> Self { setup_edel_theme(&cc.egui_ctx); let (tx, rx) = channel(); let ctx = Acr122u::create_context().expect("PC/SC Context konnte nicht erstellt werden!"); let pcsc_ctx = Arc::new(Mutex::new(ctx)); let is_busy = Arc::new(AtomicBool::new(false)); let poll_ctx = pcsc_ctx.clone(); let poll_busy = is_busy.clone(); thread::spawn(move || { poll_loop(tx, poll_ctx, poll_busy); }); Self { rx, reader_name: "Kein Lesegerät angeschlossen".to_string(), is_reader_connected: false, current_card: None, event_logs: vec!["System bereit. Bitte lege einen NFC-Tag auf das Gerät...".to_string()], active_tab: Tab::Scanner, led_red: false, led_green: true, led_blink: false, led_beep: true, mifare_dump: None, mifare_status: "Klicke auf 'Karte Auslesen', um alle Speichersektoren zu scannen.".to_string(), ndef_subtab: NdefSubTab::Text, ndef_url_input: "https://example.com".to_string(), ndef_text_input: "Hallo! Das ist meine persönliche Notiz auf dem NFC-Tag.".to_string(), ndef_text_lang: "de".to_string(), wifi_ssid: "MeinWLAN".to_string(), wifi_pass: "MeinPasswort123".to_string(), vcard_name: "Max Mustermann".to_string(), vcard_phone: "+49 170 1234567".to_string(), vcard_email: "max@beispiel.de".to_string(), vcard_company: "Meine Firma GmbH".to_string(), geo_url: "https://maps.google.com/?q=Berlin".to_string(), aar_package: "com.spotify.music".to_string(), ndef_status: "Bereit zum Beschreiben".to_string(), pwd_input: "1234".to_string(), pack_input: "0000".to_string(), pwd_status: "Gib ein 4-stelliges Passwort ein, um den Tag zu schützen.".to_string(), apdu_input: "FF 00 48 00 00".to_string(), apdu_response_log: Vec::new(), pcsc_ctx, is_busy, } } } fn setup_edel_theme(ctx: &egui::Context) { let mut style = (*ctx.style()).clone(); let mut visuals = egui::Visuals::dark(); visuals.panel_fill = egui::Color32::from_rgb(10, 13, 20); visuals.window_fill = egui::Color32::from_rgb(19, 25, 39); visuals.extreme_bg_color = egui::Color32::from_rgb(6, 8, 14); visuals.widgets.noninteractive.rounding = egui::Rounding::same(10.0); visuals.widgets.noninteractive.bg_fill = egui::Color32::from_rgb(26, 34, 50); visuals.widgets.noninteractive.bg_stroke = egui::Stroke::new(1.0_f32, egui::Color32::from_rgb(45, 58, 82)); visuals.widgets.inactive.rounding = egui::Rounding::same(10.0); visuals.widgets.inactive.bg_fill = egui::Color32::from_rgb(26, 34, 50); visuals.widgets.hovered.rounding = egui::Rounding::same(10.0); visuals.widgets.hovered.bg_fill = egui::Color32::from_rgb(45, 58, 82); visuals.widgets.hovered.bg_stroke = egui::Stroke::new(1.5_f32, egui::Color32::from_rgb(0, 242, 254)); visuals.widgets.active.rounding = egui::Rounding::same(10.0); visuals.widgets.active.bg_fill = egui::Color32::from_rgb(0, 242, 254); visuals.widgets.active.fg_stroke = egui::Stroke::new(1.0_f32, egui::Color32::BLACK); visuals.selection.bg_fill = egui::Color32::from_rgba_premultiplied(0, 242, 254, 70); style.visuals = visuals; style.spacing.item_spacing = egui::vec2(12.0, 12.0); style.spacing.button_padding = egui::vec2(16.0, 10.0); ctx.set_style(style); } fn poll_loop(tx: Sender, ctx_store: Arc>, is_busy: Arc) { let mut last_uid = String::new(); loop { thread::sleep(Duration::from_millis(600)); if is_busy.load(Ordering::SeqCst) { continue; } let ctx = ctx_store.lock().unwrap(); let reader = match Acr122u::find_acr122u(&ctx) { Ok(r) => { let _ = tx.send(CardEvent::ReaderStatus { connected: true, name: r.clone(), }); r } Err(_) => { let _ = tx.send(CardEvent::ReaderStatus { connected: false, name: "Kein Lesegerät".to_string(), }); last_uid.clear(); let _ = tx.send(CardEvent::CardRemoved); continue; } }; match Acr122u::read_card_info(&ctx, &reader) { Ok(info) => { if info.uid != last_uid { last_uid = info.uid.clone(); let _ = Acr122u::beep(&ctx, &reader, 50, 1); let _ = tx.send(CardEvent::CardDetected(info)); } } Err(_) => { if !last_uid.is_empty() { last_uid.clear(); let _ = tx.send(CardEvent::CardRemoved); } } } } } impl eframe::App for NfcApp { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { while let Ok(evt) = self.rx.try_recv() { let timestamp = chrono::Local::now().format("%H:%M:%S").to_string(); match evt { CardEvent::ReaderStatus { connected, name } => { self.is_reader_connected = connected; self.reader_name = name; } CardEvent::CardDetected(info) => { let friendly_type = match info.uid_bytes.len() { 7 => "Standard NFC-Sticker / Tag (NTAG)", 4 => "MIFARE Chipkarte / Schlüsselanhänger", _ => "Kontaktloser NFC-Chip", }; self.event_logs.insert(0, format!("[{}] 💳 NFC-Tag erkannt: ID {} ({})", timestamp, info.uid, friendly_type)); self.current_card = Some(info); } CardEvent::CardRemoved => { if self.current_card.is_some() { self.event_logs.insert(0, format!("[{}] 🚫 NFC-Tag entfernt", timestamp)); self.current_card = None; } } } } ctx.request_repaint_after(Duration::from_millis(200)); egui::TopBottomPanel::top("top_header").show(ctx, |ui| { ui.add_space(8.0); ui.horizontal(|ui| { ui.label(egui::RichText::new("💎 ACR122U NFC Studio Pro").font(egui::FontId::proportional(22.0)).strong().color(egui::Color32::from_rgb(0, 242, 254))); ui.label(egui::RichText::new("Premium Edition").font(egui::FontId::proportional(12.0)).color(egui::Color32::from_rgb(148, 163, 184))); ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { if self.is_reader_connected { ui.colored_label(egui::Color32::from_rgb(0, 230, 153), format!("🟢 {}", self.reader_name)); } else { ui.colored_label(egui::Color32::from_rgb(255, 75, 87), "🔴 Gerät nicht verbunden"); } }); }); ui.add_space(8.0); }); egui::TopBottomPanel::top("tab_bar").show(ctx, |ui| { ui.horizontal(|ui| { ui.selectable_value(&mut self.active_tab, Tab::Scanner, "📡 Live-Erkennung"); ui.selectable_value(&mut self.active_tab, Tab::Ndef, "✏️ NFC-Tag Beschreiben"); ui.selectable_value(&mut self.active_tab, Tab::Mifare, "🔍 Speicher & Klonen"); ui.selectable_value(&mut self.active_tab, Tab::Password, "🔒 Passwort-Schutz"); ui.selectable_value(&mut self.active_tab, Tab::Hardware, "🔔 LEDs & Töne"); ui.selectable_value(&mut self.active_tab, Tab::ExpertApdu, "🛠️ Experten-Befehle"); }); }); egui::CentralPanel::default().show(ctx, |ui| { match self.active_tab { Tab::Scanner => self.render_scanner_tab(ui), Tab::Ndef => self.render_ndef_tab(ui), Tab::Mifare => self.render_mifare_tab(ui), Tab::Password => self.render_password_tab(ui), Tab::Hardware => self.render_hardware_tab(ui), Tab::ExpertApdu => self.render_apdu_tab(ui), } }); } } impl NfcApp { fn render_scanner_tab(&mut self, ui: &mut egui::Ui) { ui.columns(2, |cols| { cols[0].group(|ui| { ui.heading("💳 Aufgelegter NFC-Tag"); ui.label(egui::RichText::new("Hier siehst du sofort alle Infos zur erkannten Karte.").font(egui::FontId::proportional(12.0)).color(egui::Color32::from_rgb(148, 163, 184))); ui.add_space(8.0); egui::Frame::none() .fill(egui::Color32::from_rgb(19, 25, 39)) .stroke(egui::Stroke::new(1.5_f32, egui::Color32::from_rgb(0, 242, 254))) .rounding(16.0) .inner_margin(20.0) .show(ui, |ui| { ui.horizontal(|ui| { ui.label(egui::RichText::new("📇 NFC-CHIP").font(egui::FontId::proportional(14.0)).strong().color(egui::Color32::from_rgb(246, 211, 101))); ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { ui.label(egui::RichText::new("Kontaktlos").font(egui::FontId::proportional(12.0)).color(egui::Color32::from_rgb(0, 242, 254))); }); }); ui.add_space(16.0); ui.label(egui::RichText::new("EINDEUTIGE CHIP-ID (UID):").font(egui::FontId::proportional(11.0)).color(egui::Color32::from_rgb(148, 163, 184))); if let Some(card) = &self.current_card { ui.label(egui::RichText::new(&card.uid).font(egui::FontId::monospace(26.0)).strong().color(egui::Color32::from_rgb(246, 211, 101))); ui.add_space(12.0); let friendly_type = match card.uid_bytes.len() { 7 => "Standard NFC-Sticker / Tag (NTAG213/215/216)", 4 => "MIFARE Classic Chipkarte / Schlüsselanhänger", _ => "Kontaktloser NFC-Chip", }; ui.label(format!("Chip-Typ: {}", friendly_type)); } else { ui.label(egui::RichText::new("-- -- -- --").font(egui::FontId::monospace(26.0)).color(egui::Color32::GRAY)); ui.add_space(12.0); ui.colored_label(egui::Color32::GRAY, "Bitte halte einen NFC-Tag auf das Gerät..."); } }); }); cols[1].group(|ui| { ui.heading("📜 Echtzeit Aktivitäten-Log"); ui.label(egui::RichText::new("Verlauf der Erfassungen und Aktionen.").font(egui::FontId::proportional(12.0)).color(egui::Color32::from_rgb(148, 163, 184))); ui.add_space(6.0); egui::ScrollArea::vertical().max_height(350.0).show(ui, |ui| { for log in &self.event_logs { ui.label(egui::RichText::new(log).font(egui::FontId::monospace(13.0))); } }); }); }); } fn render_ndef_tab(&mut self, ui: &mut egui::Ui) { ui.heading("✏️ NFC-Tag Beschreiben"); ui.label(egui::RichText::new("Wähle aus, was beim Dranhalten eines Smartphones passieren soll:").font(egui::FontId::proportional(12.0)).color(egui::Color32::from_rgb(148, 163, 184))); ui.add_space(8.0); ui.horizontal(|ui| { ui.selectable_value(&mut self.ndef_subtab, NdefSubTab::Text, "📝 Text & Notiz"); ui.selectable_value(&mut self.ndef_subtab, NdefSubTab::Url, "🌐 Internet-Link (URL)"); ui.selectable_value(&mut self.ndef_subtab, NdefSubTab::Wifi, "📶 WLAN Quick-Connect"); ui.selectable_value(&mut self.ndef_subtab, NdefSubTab::VCard, "🎴 Visitenkarte"); ui.selectable_value(&mut self.ndef_subtab, NdefSubTab::Geo, "🗺️ Standort / Maps"); ui.selectable_value(&mut self.ndef_subtab, NdefSubTab::AppLauncher, "📱 App Öffnen"); }); ui.add_space(12.0); let mut tlv_payload: Option> = None; match self.ndef_subtab { NdefSubTab::Text => { ui.group(|ui| { ui.label("Deine Nachricht / Notiz:"); ui.add(egui::TextEdit::multiline(&mut self.ndef_text_input).desired_rows(4).desired_width(f32::INFINITY)); ui.horizontal(|ui| { if ui.button("💾 Text als Datei speichern (.txt)").clicked() { if let Some(path) = rfd::FileDialog::new().add_filter("Textdatei", &["txt"]).save_file() { let _ = fs::write(path, &self.ndef_text_input); self.ndef_status = "Text erfolgreich auf dem PC gespeichert!".to_string(); } } if ui.button("📂 Text aus Datei laden (.txt)").clicked() { if let Some(path) = rfd::FileDialog::new().add_filter("Textdatei", &["txt"]).pick_file() { if let Ok(content) = fs::read_to_string(path) { self.ndef_text_input = content; self.ndef_status = "Text aus Datei geladen!".to_string(); } } } }); tlv_payload = Some(NdefCodec::encode_text(&self.ndef_text_input, &self.ndef_text_lang)); }); } NdefSubTab::Url => { ui.group(|ui| { ui.label("Internet-Adresse (öffnet sich beim Scannen automatisch):"); ui.text_edit_singleline(&mut self.ndef_url_input); tlv_payload = Some(NdefCodec::encode_url(&self.ndef_url_input)); }); } NdefSubTab::Wifi => { ui.group(|ui| { ui.label("WLAN Netz-Name (SSID):"); ui.text_edit_singleline(&mut self.wifi_ssid); ui.label("WLAN Passwort:"); ui.text_edit_singleline(&mut self.wifi_pass); ui.label(egui::RichText::new("💡 Smartphones verbinden sich beim Dranhalten direkt mit dem WLAN.").font(egui::FontId::proportional(11.0)).color(egui::Color32::from_rgb(148, 163, 184))); tlv_payload = Some(NdefCodec::encode_wifi(&self.wifi_ssid, &self.wifi_pass)); }); } NdefSubTab::VCard => { ui.group(|ui| { ui.label("Name:"); ui.text_edit_singleline(&mut self.vcard_name); ui.label("Telefonnummer:"); ui.text_edit_singleline(&mut self.vcard_phone); ui.label("E-Mail:"); ui.text_edit_singleline(&mut self.vcard_email); ui.label("Firma / Organisation:"); ui.text_edit_singleline(&mut self.vcard_company); ui.label(egui::RichText::new("💡 Beim Scannen wird der Kontakt direkt in das Handy-Telefonbuch gespeichert.").font(egui::FontId::proportional(11.0)).color(egui::Color32::from_rgb(148, 163, 184))); tlv_payload = Some(NdefCodec::encode_vcard(&self.vcard_name, &self.vcard_phone, &self.vcard_email, &self.vcard_company)); }); } NdefSubTab::Geo => { ui.group(|ui| { ui.label("Google Maps Standorts-Link:"); ui.text_edit_singleline(&mut self.geo_url); tlv_payload = Some(NdefCodec::encode_url(&self.geo_url)); }); } NdefSubTab::AppLauncher => { ui.group(|ui| { ui.label("Android App-Name (z. B. com.spotify.music):"); ui.text_edit_singleline(&mut self.aar_package); tlv_payload = Some(NdefCodec::encode_aar(&self.aar_package)); }); } } ui.add_space(15.0); if ui.button("⚡ Auf NFC-Tag Schreiben").clicked() { if let Some(tlv) = tlv_payload { self.is_busy.store(true, Ordering::SeqCst); thread::sleep(Duration::from_millis(50)); let pcsc = self.pcsc_ctx.lock().unwrap(); if let Ok(reader) = Acr122u::find_acr122u(&pcsc) { if let Ok(mut card) = Acr122u::connect_card_with_ctx(&pcsc, &reader) { let _ = NtagHandler::ensure_cc_header(&pcsc, &reader, &mut card); let mut page = 4; let mut err = false; for chunk in tlv.chunks(4) { let mut p_data = [0u8; 4]; p_data[..chunk.len()].copy_from_slice(chunk); if NtagHandler::write_page(&pcsc, &reader, &mut card, page, &p_data).is_err() { err = true; self.ndef_status = format!("Schreibfehler auf Seite {}", page); break; } page += 1; } if !err { let _ = Acr122u::beep(&pcsc, &reader, 100, 2); self.ndef_status = "✅ Inhalt erfolgreich auf den NFC-Tag geschrieben!".to_string(); } } else { self.ndef_status = "❌ Karte konnte nicht verbunden werden!".to_string(); } } self.is_busy.store(false, Ordering::SeqCst); } } ui.add_space(10.0); ui.colored_label(egui::Color32::from_rgb(0, 242, 254), &self.ndef_status); } fn render_mifare_tab(&mut self, ui: &mut egui::Ui) { ui.heading("🔍 Speicher Auslesen & Klonen"); ui.label(egui::RichText::new("Lies den kompletten Speicherinhalt von MIFARE-Karten aus und speichere ihn ab.").font(egui::FontId::proportional(12.0)).color(egui::Color32::from_rgb(148, 163, 184))); ui.add_space(10.0); ui.horizontal(|ui| { if ui.button("🔄 Karte Auslesen").clicked() { self.is_busy.store(true, Ordering::SeqCst); thread::sleep(Duration::from_millis(50)); let pcsc = self.pcsc_ctx.lock().unwrap(); if let Ok(reader) = Acr122u::find_acr122u(&pcsc) { match MifareHandler::dump_card(&pcsc, &reader) { Ok(dump) => { self.mifare_dump = Some(dump); self.mifare_status = "Auslesen erfolgreich!".to_string(); } Err(e) => { self.mifare_status = format!("Fehler beim Auslesen: {}", e); } } } self.is_busy.store(false, Ordering::SeqCst); } if let Some(dump) = &self.mifare_dump { if ui.button("💾 Als Backup speichern (.json)").clicked() { if let Some(path) = rfd::FileDialog::new().add_filter("JSON Backup", &["json"]).save_file() { if let Ok(json) = serde_json::to_string_pretty(dump) { let _ = fs::write(path, json); self.mifare_status = "Backup-Datei gespeichert!".to_string(); } } } } if ui.button("📂 Backup aus Datei laden").clicked() { if let Some(path) = rfd::FileDialog::new().add_filter("JSON Backup", &["json"]).pick_file() { if let Ok(content) = fs::read_to_string(path) { if let Ok(loaded_dump) = serde_json::from_str::(&content) { self.mifare_dump = Some(loaded_dump); self.mifare_status = "Backup geladen!".to_string(); } } } } }); ui.colored_label(egui::Color32::from_rgb(0, 242, 254), &self.mifare_status); ui.add_space(10.0); if let Some(dump) = &self.mifare_dump { egui::ScrollArea::vertical().show(ui, |ui| { for sec in &dump.sectors { ui.group(|ui| { ui.label(egui::RichText::new(format!("Speicherblock {} (Key: {})", sec.sector_number, sec.key_a.as_deref().unwrap_or("Schlüsselgeschützt"))).strong()); for b in &sec.blocks { let text_color = if b.is_trailer { egui::Color32::from_rgb(214, 188, 250) } else { egui::Color32::WHITE }; ui.label(egui::RichText::new(format!("Block {:02}: {} | {}", b.block_number, b.hex_data, b.ascii_data)).font(egui::FontId::monospace(12.0)).color(text_color)); } }); } }); } } fn render_password_tab(&mut self, ui: &mut egui::Ui) { ui.heading("🔒 Passwort-Schutz & Tag Versiegeln"); ui.label(egui::RichText::new("Schütze deinen NFC-Tag vor unbefugtem Überschreiben.").font(egui::FontId::proportional(12.0)).color(egui::Color32::from_rgb(148, 163, 184))); ui.add_space(10.0); ui.group(|ui| { ui.label("4-stelliges Passwort (z. B. 1234):"); ui.text_edit_singleline(&mut self.pwd_input); ui.add_space(10.0); if ui.button("🔐 Passwort auf Tag aktivieren").clicked() { self.is_busy.store(true, Ordering::SeqCst); thread::sleep(Duration::from_millis(50)); let pcsc = self.pcsc_ctx.lock().unwrap(); if let Ok(reader) = Acr122u::find_acr122u(&pcsc) { if let Ok(mut card) = Acr122u::connect_card_with_ctx(&pcsc, &reader) { let pwd_bytes = self.pwd_input.as_bytes(); if pwd_bytes.len() == 4 { let mut p_arr = [0u8; 4]; p_arr.copy_from_slice(pwd_bytes); if NtagHandler::write_page(&pcsc, &reader, &mut card, 134, &p_arr).is_ok() { self.pwd_status = "✅ Passwort erfolgreich auf dem Tag aktiviert!".to_string(); let _ = Acr122u::beep(&pcsc, &reader, 100, 2); } else { self.pwd_status = "❌ Fehler beim Aktivieren des Passworts!".to_string(); } } else { self.pwd_status = "Das Passwort muss genau 4 Zeichen lang sein!".to_string(); } } } self.is_busy.store(false, Ordering::SeqCst); } }); ui.add_space(15.0); ui.colored_label(egui::Color32::from_rgb(0, 242, 254), &self.pwd_status); } fn render_hardware_tab(&mut self, ui: &mut egui::Ui) { ui.heading("🔔 LEDs & Signal-Töne des Lesegeräts"); ui.label(egui::RichText::new("Steuere die Hardware-Signale deines ACR122U Geräts.").font(egui::FontId::proportional(12.0)).color(egui::Color32::from_rgb(148, 163, 184))); ui.add_space(10.0); ui.checkbox(&mut self.led_red, "Rote LED aktivieren"); ui.checkbox(&mut self.led_green, "Grüne LED aktivieren"); ui.checkbox(&mut self.led_blink, "Blinken aktivieren"); ui.checkbox(&mut self.led_beep, "Ton beim Schalten abspielen"); ui.add_space(12.0); if ui.button("⚡ LED & Ton-Einstellungen Anwenden").clicked() { let pcsc = self.pcsc_ctx.lock().unwrap(); let config = LedBuzzerConfig { red: self.led_red, green: self.led_green, blink_red: self.led_blink, blink_green: self.led_blink, blink_count: if self.led_blink { 4 } else { 0 }, t1_duration_ms: 100, t2_duration_ms: 100, buzzer_mode: if self.led_beep { 1 } else { 0 }, }; if let Ok(reader) = Acr122u::find_acr122u(&pcsc) { let _ = Acr122u::set_led_buzzer(&pcsc, &reader, &config); } } ui.add_space(20.0); ui.label("Signalton testen:"); ui.horizontal(|ui| { if ui.button("🔊 1x Signalton").clicked() { let pcsc = self.pcsc_ctx.lock().unwrap(); if let Ok(reader) = Acr122u::find_acr122u(&pcsc) { let _ = Acr122u::beep(&pcsc, &reader, 100, 1); } } if ui.button("🔊 2x Bestätigungston").clicked() { let pcsc = self.pcsc_ctx.lock().unwrap(); if let Ok(reader) = Acr122u::find_acr122u(&pcsc) { let _ = Acr122u::beep(&pcsc, &reader, 100, 2); } } }); } fn render_apdu_tab(&mut self, ui: &mut egui::Ui) { ui.heading("🛠️ Experten-Befehle (Rohdaten / APDU)"); ui.label(egui::RichText::new("Manuelle Befehlseingabe für fortgeschrittene Nutzer.").font(egui::FontId::proportional(12.0)).color(egui::Color32::from_rgb(148, 163, 184))); ui.add_space(10.0); ui.label("Befehl in Hexadezimal (z. B. FF 00 48 00 00):"); ui.text_edit_singleline(&mut self.apdu_input); if ui.button("Befehl Senden").clicked() { let pcsc = self.pcsc_ctx.lock().unwrap(); if let Ok(reader) = Acr122u::find_acr122u(&pcsc) { if let Ok(card) = Acr122u::connect_card_with_ctx(&pcsc, &reader).or_else(|_| Acr122u::connect_direct_with_ctx(&pcsc, &reader)) { if let Ok(bytes) = hex::decode(self.apdu_input.replace(" ", "")) { match Acr122u::transmit(&card, &bytes) { Ok((data, sw1, sw2)) => { let entry = format!("> {}\n< DATEN: {}\n< STATUS: {:02X}{:02X}", self.apdu_input, hex::encode_upper(&data), sw1, sw2); self.apdu_response_log.insert(0, entry); } Err(e) => { self.apdu_response_log.insert(0, format!("❌ FEHLER: {}", e)); } } } } } } ui.add_space(15.0); egui::ScrollArea::vertical().show(ui, |ui| { for log in &self.apdu_response_log { ui.label(egui::RichText::new(log).font(egui::FontId::monospace(13.0))); ui.separator(); } }); } } fn main() -> eframe::Result<()> { let options = eframe::NativeOptions { viewport: egui::ViewportBuilder::default() .with_title("ACR122U NFC Studio Pro (Transparent Reconnect)") .with_inner_size([1150.0, 780.0]), ..Default::default() }; eframe::run_native( "nfc_tool", options, Box::new(|cc| Ok(Box::new(NfcApp::new(cc)))), ) }