// hide console in release mode #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] use std::{process, time::SystemTime}; use anyhow::{Result, bail}; use app::Application; use clap::Parser; use emulator::EmulatorBuilder; use thread_priority::{ThreadBuilder, ThreadPriority}; use tracing::error; use tracing_subscriber::{EnvFilter, Layer, layer::SubscriberExt, util::SubscriberInitExt}; use winit::event_loop::{ControlFlow, EventLoop}; use crate::{ config::{AppConfig, CliArgs, SimConfig}, emulator::SimId, persistence::Persistence, }; mod app; mod audio; mod config; mod controller; mod emulator; mod filepicker; mod gdbserver; mod graphics; mod images; mod input; mod memory; mod persistence; mod profiler; mod window; fn init_logger() { let directives = std::env::var("RUST_LOG").unwrap_or("error,lemur=info".into()); let filter = EnvFilter::builder().parse_lossy(directives); let layer = tracing_subscriber::fmt::layer().with_filter(filter); tracing_subscriber::registry().with(layer).init(); } fn set_panic_handler() { std::panic::set_hook(Box::new(|info| { let mut message = String::new(); if let Some(msg) = info.payload().downcast_ref::<&str>() { message += &format!("{msg}\n"); } else if let Some(msg) = info.payload().downcast_ref::() { message += &format!("{msg}\n"); } if let Some(location) = info.location() { message += &format!( " in file '{}' at line {}\n", location.file(), location.line() ); } let backtrace = std::backtrace::Backtrace::force_capture(); message += &format!("stack trace:\n{backtrace:#}\n"); eprint!("{message}"); let Some(project_dirs) = directories::ProjectDirs::from("com", "virtual-boy", "Lemur") else { return; }; let data_dir = project_dirs.data_dir(); if std::fs::create_dir_all(data_dir).is_err() { return; } let timestamp = SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) .unwrap() .as_millis(); let logfile_name = format!("crash-{timestamp}.txt"); let _ = std::fs::write(data_dir.join(logfile_name), message); })); } #[cfg(windows)] fn set_process_priority_to_high() -> Result<()> { use windows::Win32::{Foundation, System::Threading}; let process = unsafe { Threading::GetCurrentProcess() }; unsafe { Threading::SetPriorityClass(process, Threading::HIGH_PRIORITY_CLASS)? }; unsafe { Foundation::CloseHandle(process)? }; Ok(()) } fn main() -> Result<()> { init_logger(); set_panic_handler(); #[cfg(windows)] set_process_priority_to_high()?; let args = CliArgs::parse(); let persistence = Persistence::new(); let (mut builder, client) = EmulatorBuilder::new(); if let Some(path) = &args.rom { builder = builder.with_rom(path); } if args.debug_port.is_some() { if args.rom.is_none() { bail!("to start debugging, please select a game."); } builder = builder.start_paused(true); } if args.profile { builder = builder.start_paused(true) } let mut config = AppConfig::load(&persistence); if config.update(&args) { let _ = config.save(&persistence); } let p1 = SimConfig::load(&persistence, SimId::Player1); let p2 = SimConfig::load(&persistence, SimId::Player2); let watch = args.watch; builder = builder .with_audio_on(p1.audio_enabled, p2.audio_enabled) .with_watch_rom(watch, watch); ThreadBuilder::default() .name("Emulator".to_owned()) .priority(ThreadPriority::Max) .spawn_careless(move || { let mut emulator = match builder.build() { Ok(e) => e, Err(error) => { error!(%error, "Error initializing emulator"); process::exit(1); } }; emulator.run(); })?; let event_loop = EventLoop::with_user_event().build().unwrap(); event_loop.set_control_flow(ControlFlow::Poll); let proxy = event_loop.create_proxy(); let handle = event_loop.owned_display_handle(); event_loop.run_app(&mut Application::new( client, proxy, handle, persistence, args, config, ))?; Ok(()) }