// hide console in release mode #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] use std::{path::PathBuf, 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}; mod app; mod audio; mod controller; mod emulator; mod gdbserver; mod graphics; mod images; mod input; mod memory; mod persistence; mod profiler; mod window; #[derive(Parser)] struct Args { /// The path to a virtual boy ROM to run. rom: Option, /// Start a GDB/LLDB debug server on this port. #[arg(short, long)] debug_port: Option, /// Enable profiling a game #[arg(short, long)] profile: bool, } 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 = Args::parse(); 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) } 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(); event_loop.run_app(&mut Application::new( client, proxy, args.debug_port, args.profile, ))?; Ok(()) }